抓取数据并与 HTML 中呈现的网页进行交互
Scrape data and interact with webpage rendered in HTML
我正在尝试从 a FanGraphs webpage 中抓取一些数据并与页面本身进行交互。由于页面上有很多按钮和下拉菜单来缩小我的搜索结果,我需要能够在 HTML 中找到相应的元素。但是,当我尝试使用 'classic' 方法并使用 requests
和 urllib.requests
等模块时,包含我需要的数据的 HTML 部分没有出现。
HTML 片段
这是 HTML 的一部分,其中包含我需要的元素。
<div id="root-season-grid">
<div class="season-grid-wrapper">
<div class="season-grid-title">Season Stat Grid</div>
<div class="season-grid-controls">
<div class="season-grid-controls-button-row">
<div class="fgButton button-green active isActive">Batting</div>
<div class="fgButton button-green">Pitching</div>
<div class="spacer-v-20"></div>
<div class="fgButton button-green active isActive">Normal</div>
<div class="fgButton button-green">Normal & Changes</div>
<div class="fgButton button-green">Year-to-Year Changes</div>
</div>
</div>
</div>
</div>
</div>
完整的 CSS 路径:
html > body > div#wrapper > div#content > div#root-season-grid div.season-grid-wrapper > div.season-grid-controls > div.season-grid-controls-button-row
尝试次数
requests
和 bs4
>>> res = requests.get("https://fangraphs.com/leaders/season-stat-grid")
>>> soup = bs4.BeautifulSoup4(res.text, features="lxml")
>>> soup.select("#root-season-grid")
[<div id="root-season-grid"></div>]
>>> soup.select(".season-grid-wrapper")
[]
因此 bs4
能够找到 <div id="root-season-grid"></div>
元素,但找不到该元素的任何后代。
urllib
和 lxml
>>> res = urllib.request.urlopen("https://fangraphs.com/leaders/season-stat-grid")
>>> parser = lxml.etree.HTMLParser()
>>> tree = lxml.etree.parse(res, parser)
>>> tree.xpath("//div[@id='root-season-grid']")
[<Element div at 0x131e1b3f8c0>]
>>> tree.xpath("//div[@class='season-grid-wrapper']")
[]
同样,找不到 div
元素的后代,这次是 lxml
。
我开始考虑是否应该使用不同的 URL 地址来传递给 requests.get()
和 urlopen()
,所以我创建了一个 selenium
远程浏览器, browser
,然后将 browser.current_url
传递给两个函数。不幸的是,结果是一样的。
selenium
我确实注意到,使用selenium.find_element_by_*
和selenium.find_elements_by_*
能够找到元素,所以我开始使用它。但是,这样做会占用大量内存,而且速度极慢。
selenium
和 bs4
由于 selenium.find_element_by_*
正常工作,我想出了一个非常 hacky 'solution'。我使用 "*"
CSS 选择器选择了完整的 HTML 然后将其传递给 bs4.BeautifulSoup()
>>> browser = selenium.webdriver.Firefox()
>>> html_elem = browser.find_element_by_css_selector("*")
>>> html = html_elem.get_attribute("innerHTML")
>>> soup = bs4.BeautifulSoup(html, features="lxml")
>>> soup.select("#root-season-grid")
[<div id="root-season-grid"><div class="season-grid-wrapper">...</div></div>]
>>> soup.select(".season-grid-wrapper")
[<div class="season-grid-wrapper">...</div>]
所以这最后一次尝试有点成功,因为我能够获得我需要的元素。然而,在 运行 一堆单元测试和一些模块集成测试之后,我意识到这是多么不一致。
问题
经过大量研究后,我总结了尝试(1)和(2)不起作用以及尝试(3)不一致的原因是页面中的table被渲染了JavaScript,以及按钮和下拉菜单。这也解释了为什么当您单击 查看页面源代码 时上面的 HTML 不存在。看起来,当 requests.get()
和 urlopen()
被调用时, JavaScript 没有完全渲染, bs4
+selenium
是否工作取决于 JavaScript 渲染。是否有任何 Python 库可以在 返回 HTML 内容之前渲染 JavaScript ?
希望这个问题不会太长。我试图在不牺牲清晰度的情况下尽可能地浓缩。
只需从 Selenium 获取 page_source 并将其传递给 bs4。
browser.get("https://fangraphs.com/leaders/season-stat-grid")
soup = bs4.BeautifulSoup(browser.page_source, features="lxml")
print(soup.select("#root-season-grid"))
我建议使用他们的 api 但是 https://www.fangraphs.com/api/leaders/season-grid/data?position=B&seasonStart=2011&seasonEnd=2019&stat=WAR&pastMinPt=400&curMinPt=0&mode=normal
我正在尝试从 a FanGraphs webpage 中抓取一些数据并与页面本身进行交互。由于页面上有很多按钮和下拉菜单来缩小我的搜索结果,我需要能够在 HTML 中找到相应的元素。但是,当我尝试使用 'classic' 方法并使用 requests
和 urllib.requests
等模块时,包含我需要的数据的 HTML 部分没有出现。
HTML 片段
这是 HTML 的一部分,其中包含我需要的元素。
<div id="root-season-grid">
<div class="season-grid-wrapper">
<div class="season-grid-title">Season Stat Grid</div>
<div class="season-grid-controls">
<div class="season-grid-controls-button-row">
<div class="fgButton button-green active isActive">Batting</div>
<div class="fgButton button-green">Pitching</div>
<div class="spacer-v-20"></div>
<div class="fgButton button-green active isActive">Normal</div>
<div class="fgButton button-green">Normal & Changes</div>
<div class="fgButton button-green">Year-to-Year Changes</div>
</div>
</div>
</div>
</div>
</div>
完整的 CSS 路径:
html > body > div#wrapper > div#content > div#root-season-grid div.season-grid-wrapper > div.season-grid-controls > div.season-grid-controls-button-row
尝试次数
requests
和bs4
>>> res = requests.get("https://fangraphs.com/leaders/season-stat-grid")
>>> soup = bs4.BeautifulSoup4(res.text, features="lxml")
>>> soup.select("#root-season-grid")
[<div id="root-season-grid"></div>]
>>> soup.select(".season-grid-wrapper")
[]
因此 bs4
能够找到 <div id="root-season-grid"></div>
元素,但找不到该元素的任何后代。
urllib
和lxml
>>> res = urllib.request.urlopen("https://fangraphs.com/leaders/season-stat-grid")
>>> parser = lxml.etree.HTMLParser()
>>> tree = lxml.etree.parse(res, parser)
>>> tree.xpath("//div[@id='root-season-grid']")
[<Element div at 0x131e1b3f8c0>]
>>> tree.xpath("//div[@class='season-grid-wrapper']")
[]
同样,找不到 div
元素的后代,这次是 lxml
。
我开始考虑是否应该使用不同的 URL 地址来传递给 requests.get()
和 urlopen()
,所以我创建了一个 selenium
远程浏览器, browser
,然后将 browser.current_url
传递给两个函数。不幸的是,结果是一样的。
selenium
我确实注意到,使用selenium.find_element_by_*
和selenium.find_elements_by_*
能够找到元素,所以我开始使用它。但是,这样做会占用大量内存,而且速度极慢。
selenium
和bs4
由于 selenium.find_element_by_*
正常工作,我想出了一个非常 hacky 'solution'。我使用 "*"
CSS 选择器选择了完整的 HTML 然后将其传递给 bs4.BeautifulSoup()
>>> browser = selenium.webdriver.Firefox()
>>> html_elem = browser.find_element_by_css_selector("*")
>>> html = html_elem.get_attribute("innerHTML")
>>> soup = bs4.BeautifulSoup(html, features="lxml")
>>> soup.select("#root-season-grid")
[<div id="root-season-grid"><div class="season-grid-wrapper">...</div></div>]
>>> soup.select(".season-grid-wrapper")
[<div class="season-grid-wrapper">...</div>]
所以这最后一次尝试有点成功,因为我能够获得我需要的元素。然而,在 运行 一堆单元测试和一些模块集成测试之后,我意识到这是多么不一致。
问题
经过大量研究后,我总结了尝试(1)和(2)不起作用以及尝试(3)不一致的原因是页面中的table被渲染了JavaScript,以及按钮和下拉菜单。这也解释了为什么当您单击 查看页面源代码 时上面的 HTML 不存在。看起来,当 requests.get()
和 urlopen()
被调用时, JavaScript 没有完全渲染, bs4
+selenium
是否工作取决于 JavaScript 渲染。是否有任何 Python 库可以在 返回 HTML 内容之前渲染 JavaScript ?
希望这个问题不会太长。我试图在不牺牲清晰度的情况下尽可能地浓缩。
只需从 Selenium 获取 page_source 并将其传递给 bs4。
browser.get("https://fangraphs.com/leaders/season-stat-grid")
soup = bs4.BeautifulSoup(browser.page_source, features="lxml")
print(soup.select("#root-season-grid"))
我建议使用他们的 api 但是 https://www.fangraphs.com/api/leaders/season-grid/data?position=B&seasonStart=2011&seasonEnd=2019&stat=WAR&pastMinPt=400&curMinPt=0&mode=normal