PyQt5 QWebEnginePage - Можно ли редактировать HTML, чтобы открыть раскрывающийся список? - PullRequest
3 голосов
/ 10 октября 2019

Я пытаюсь разработать устойчивый веб-скрипт для получения списка всех продуктов с веб-сайта. Ссылки на категории продуктов находятся в выпадающих (или расширяемых) элементах на веб-странице. Я использую PyQt5 для эмуляции клиента перед извлечением HTML и преобразованием его в текст с помощью Beautiful Soup.

Например, если вы посещаете сайт в браузере, вам нужно будет нажать кнопку рядом св верхнем левом углу страницы, чтобы открыть список категорий, который появляется в левой части экрана (я буду называть это «боковой панелью»). В каждой из этих категорий при нажатии появляется список более конкретных категорий, каждая из которых содержит ссылку, которую я пытаюсь получить с помощью своего кода (я буду называть их «подкатегориями»).

Исходные элементы списка категорий появляются в моем 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-адрес веб-сайта и снимок экрана веб-страницы, чтобы помочь прояснить, не уверенный, считается ли это хорошей практикой или разрешеноПереполнение стека (я здесь новичок!).

Дополнительную информацию о методе, который я пытаюсь использовать, см. в следующих статьях:

Видео динамического скрепинга Sentdex PyQt4

Изменения библиотеки PyQt4 в PyQt5

1 Ответ

1 голос
/ 11 октября 2019

Если вы загрузите HTML-код со страницы, вы увидите, что почти вся страница создана с использованием javascript, поэтому Beautiful Soup не является подходящим инструментом, поскольку он служит только для анализа HTML. В этом случае решение состоит в том, чтобы реализовать логику через javascript, используя runJavaScript() метод QWebEnginePage:

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))

Вывод:

[
    {
        "items": [
            {
                "name": "Fall Flavors Shop",
                "url": "https://grocery.walmart.com/cp/Flavors%20of%20Fall/9576778812"
            },
            {
                "name": "Baking Center",
                "url": "https://grocery.walmart.com/browse?shelfId=3433056320"
            },
            {
                "name": "Peak Season Produce",
                "url": "https://grocery.walmart.com/browse?shelfId=4881154845"
            },
# ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...