【问题标题】:Scrape data and interact with webpage rendered in HTML抓取数据并与以 HTML 呈现的网页进行交互
【发布时间】:2021-06-05 08:58:23
【问题描述】:

我正在尝试从a FanGraphs webpage 中提取一些数据并与页面本身进行交互。由于页面上有很多按钮和下拉菜单来缩小我的搜索结果,我需要能够在 HTML 中找到相应的元素。但是,当我尝试使用“经典”方法并使用 requestsurllib.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 &amp; Changes</div>
                    <div class="fgButton button-green">Year-to-Year Changes</div>
                </div>
            </div>
        </div>
    </div>
</div>

完整的 CSS 路径:

html &gt; body &gt; div#wrapper &gt; div#content &gt; div#root-season-grid div.season-grid-wrapper &gt; div.season-grid-controls &gt; div.season-grid-controls-button-row

尝试

  1. requestsbs4
>>> 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 能够找到&lt;div id="root-season-grid"&gt;&lt;/div&gt; 元素,但找不到该元素的任何后代。

  1. urlliblxml
>>> 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 传递给这两个函数.不幸的是,结果是相同的。

  1. selenium

确实注意到,使用 selenium.find_element_by_*selenium.find_elements_by_* 能够找到元素,所以我开始使用它。但是,这样做会占用大量内存并且速度非常慢。

  1. seleniumbs4

由于selenium.find_element_by_* 工作正常,我想出了一个非常老套的“解决方案”。我使用 "*" 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>]

所以最后一次尝试有点成功,因为我能够获得我需要的元素。然而,在为模块运行了一堆单元测试和一些集成测试之后,我意识到这是多么不一致。

问题

经过一番研究,我总结出Attempt(1)和(2)不起作用以及Attempt(3)不一致的原因是因为页面中的表格是由JavaScript渲染的,以及按钮和下拉菜单。这也解释了为什么当您单击 查看页面源代码 时上面的 HTML 不存在。看起来,当调用requests.get()urlopen() 时,JavaScript 没有完全渲染,bs4+selenium 是否有效取决于 JavaScript 渲染的速度。是否有任何 Python 库可以在返回 HTML 内容之前渲染 JavaScript

希望这不是一个太长的问题。我试图在不牺牲清晰度的情况下尽可能地浓缩。

【问题讨论】:

  • 如果此站点是动态 Javascript 并且您希望通过点击与其交互,则使用 Selenium 代替此站点。如果需要,还有一个 api。
  • @ArundeepChohan 我一直在使用 Selenium 来自动点击等,但如果你愿意的话,最耗时的部分是检索“当前设置”。由于我计划将其用于需要抓取更多数据的大型项目,因此我不希望出现当前存在的不一致问题。

标签: python python-3.x selenium beautifulsoup lxml


【解决方案1】:

只需从 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&amp;seasonStart=2011&amp;seasonEnd=2019&amp;stat=WAR&amp;pastMinPt=400&amp;curMinPt=0&amp;mode=normal

【讨论】:

  • 这比使用"*" CSS 选择器要好得多。我还在bs4.BeautifulSoup() 调用之前添加了WebDriverWait() 调用,这几乎消除了我之前面临的不一致。
猜你喜欢
  • 2021-05-18
  • 2018-01-12
  • 2019-01-22
  • 2018-04-06
  • 2018-08-09
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多