PyQt5 QWebEnginePage-可以编辑HTML以打开下拉菜单吗?

时间:2019-10-09 22:39:29

标签: python html web-scraping beautifulsoup pyqt5

我正在尝试开发一种可持续的网络抓取脚本,以从网站获取所有产品的列表。产品类别链接位于网页上的下拉(或可扩展)元素中。我正在使用PyQt5来模拟客户端,然后再提取html并将其转换为带有Beautiful Soup的文本。

例如,如果您正在浏览器上访问该网站,则必须单击页面左上角附近的按钮以打开从屏幕左侧弹出的类别列表(我将将此称为“侧栏”)。在这些类别的每个类别中,当单击它们时,都有一个更具体的类别列表,每个类别都包含我尝试使用自己的代码获取的链接(我将其称为“子类别”)。

即使隐藏侧边栏,初始类别列表元素也会出现在我的美丽汤中,但是除非扩展了子类别标题,否则子类别元素仍然隐藏(因此,它们不会显示在我的汤中) )。我已通过手动检查Chrome浏览器中的元素来确认这一点。这是网页HTML的摘录,其中包含我自己的注释以帮助说明:

<div aria-label="Fruits &amp; Vegetables" data-automation-id="taxonomy-toggle-Fruits &amp; Vegetables">
  <button aria-disabled="false" aria-expanded="false" class="NavSection__sectionBtn___1_cAs" data- 
   automation-id="nav-section-toggle" tabindex="-1"> #Initial category that contains sub-categories
  </button>
  <div>
  </div> #Contains the links I need, but doesn't populate HTML text unless sub-category element is expanded
</div>

这是子类别元素已扩展的外观:

<div aria-label="Fruits &amp; Vegetables" data-automation-id="taxonomy-toggle-Fruits &amp; Vegetables">
      <button aria-disabled="true" aria-expanded="true" class="NavSection__sectionBtn___1_cAs" data- 
       automation-id="nav-section-toggle" tabindex="-1"> #Initial category that contains sub-categories
      </button>
      <div>
         <ul class>
           <li class = "NavSection__sectionLink__rbr40> </li>
           <li class = "NavSection__sectionLink__rbr40> </li> #can open each li element up to acquire href link
           <li class = "NavSection__sectionLink__rbr40> </li>
         </ul>
      </div>
</div>

这是我的代码:

import bs4 as bs
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEnginePage

#act as a client via Qt5 to acquire javascript elements from webpage
class Page(QWebEnginePage):

    def __init__(self, url):
        self.app = QApplication(sys.argv)
        QWebEnginePage.__init__(self)
        self.html = ''
        self.loadFinished.connect(self._on_load_finished)
        self.load(QUrl(url))
        self.app.exec_()

    def _on_load_finished(self):
        self.html = self.toHtml(self.callable)
        print("Load Finished")

    def callable(self, html_str):   
        self.html = html_str
        self.app.quit()

page = Page("https://grocery.walmart.com")
soup = bs.BeautifulSoup(page.html, 'lxml')
print(soup.prettify())

我知道,如果aria-expanded元素的aria-disabled<button>属性从“ False”更改为“ True”,则子类别<li>元素将出现在HTML中。我通过Chrome浏览器中的手动检查确认了这一点。

我的问题是,是否可以从href元素中获取<li>?我的假设是,在初始解析之后,我必须编辑HTML才能将aria属性从“ False”更改为“ True”,然后使用这些更改重新解析HTML。如果不是,是否有其他方法可以从Selenium之外的网页上获取这些元素?我正在尝试使用一种更精简的方法(不打开浏览器窗口等)。

我可以提供实际的网站URL和该屏幕截图以帮助澄清,不确定是否认为这是好的做法或是否在Stack Overflow上允许使用(我是新来的!)。

有关我要使用的方法的更多背景信息,请参见以下内容:

Sentdex's PyQt4 Dynamic Scraping Video

PyQt4 to PyQt5 library changes

1 个答案:

答案 0 :(得分:1)

如果从页面下载HTML,您会看到几乎整个页面都是使用javascript创建的,因此Beautiful Soup不是正确的工具,因为它仅用于分析HTML。在这种情况下,解决方案是使用runJavaScript()QWebEnginePage方法通过javascript实现逻辑:

send_messages

输出:

from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets


class WalmartGroceryPage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._results = None
        self.loadFinished.connect(self._on_load_finished)
        self.setUrl(QtCore.QUrl("https://grocery.walmart.com"))

    @QtCore.pyqtSlot(bool)
    def _on_load_finished(self, ok):
        if ok:
            self.runJavaScript(
                """
                function scraper_script(){
                    var results = []
                    self.document.getElementById("mobileNavigationBtn").click();
                    var elements = document.getElementsByClassName("NavSection__sectionBtn___1_cAs");
                    for (const element of elements) {
                        element.click();
                        var items = [];
                        var sub_elements = document.getElementsByClassName("MobileNavigation__navLink___2-m6_");
                        for (const e of sub_elements) {
                            var d = {"name": e.innerText, "url": e.href};
                            items.push(d);
                        }
                        var data = {"name": element.innerText, "items": items};
                        results.push(data);
                    }
                    return results;
                }
                scraper_script();
                """,
                self.results_callback,
            )

    def results_callback(self, value):
        self._results = value
        QtCore.QCoreApplication.quit()

    @property
    def results(self):
        return self._results


if __name__ == "__main__":
    import sys
    import json

    # sys.argv.append("--remote-debugging-port=8000")
    app = QtWidgets.QApplication(sys.argv)

    page = WalmartGroceryPage()
    ret = app.exec_()
    results = page.results

    print(json.dumps(results, indent=4))