Веб-парсинг страницы JavaScript с помощью Python

Я пытаюсь разработать простой парсер. Я хочу извлечь текст без HTML-кода. Фактически, я добиваюсь этой цели, но я видел, что на некоторых страницах, где загружен JavaScript, я не получал хороших результатов.

Например, если какой-то код JavaScript добавляет текст, я его не вижу, потому что, когда я вызываю

response = urllib2.urlopen(request)

Я получаю исходный текст без добавленного (потому что JavaScript выполняется в клиенте).

Итак, я ищу идеи для решения этой проблемы.


person mocopera    schedule 08.11.2011    source источник
comment
Похоже, вам может понадобиться что-то более тяжелое, попробуйте Selenium или Watir.   -  person wim    schedule 08.11.2011
comment
Я успешно сделал это на Java (я использовал набор инструментов Cobra lobobrowser.org/cobra.jsp) Поскольку вы хотите взломать Python (всегда хороший выбор), я рекомендую эти два варианта: - packtpub.com/article/web-scraping-with-python-part-2 - blog.databigbang.com/web-scraping-ajax-and-javascript-sites   -  person bpgergo    schedule 08.11.2011
comment
Обратите внимание, что ответ с самым высоким рейтингом последний раз обновлялся в 2017 г. и устарел по состоянию на 2021 г., поскольку у PhantomJS и dryscrape есть устарел. Я рекомендую прочитать всю цепочку, прежде чем пробовать один из рекомендуемых ею методов.   -  person ggorlen    schedule 31.03.2021


Ответы (17)


РЕДАКТИРОВАТЬ 30 декабря 2017 г .: Этот ответ появляется в первых результатах поиска Google, поэтому я решил его обновить. Старый ответ все еще в конце.

dryscape больше не поддерживается, и разработчики библиотеки dryscape рекомендуют только Python 2. Я обнаружил, что использование библиотеки Python Selenium с Phantom JS в качестве веб-драйвера достаточно быстро и легко для выполнения работы.

После установки Phantom JS убедитесь, что двоичный файл phantomjs доступен по текущему пути:

phantomjs --version
# result:
2.1.1

Пример

В качестве примера я создал образец страницы со следующим кодом HTML. (ссылка):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Javascript scraping test</title>
</head>
<body>
  <p id='intro-text'>No javascript support</p>
  <script>
     document.getElementById('intro-text').innerHTML = 'Yay! Supports javascript';
  </script> 
</body>
</html>

без javascript он говорит: No javascript support и с javascript: Yay! Supports javascript

Парсинг без поддержки JS:

import requests
from bs4 import BeautifulSoup
response = requests.get(my_url)
soup = BeautifulSoup(response.text)
soup.find(id="intro-text")
# Result:
<p id="intro-text">No javascript support</p>

Парсинг с поддержкой JS:

from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get(my_url)
p_element = driver.find_element_by_id(id_='intro-text')
print(p_element.text)
# result:
'Yay! Supports javascript'

Вы также можете использовать библиотеку Python dryscrape для очистки веб-сайтов, управляемых javascript.

Парсинг с поддержкой JS:

import dryscrape
from bs4 import BeautifulSoup
session = dryscrape.Session()
session.visit(my_url)
response = session.body()
soup = BeautifulSoup(response)
soup.find(id="intro-text")
# Result:
<p id="intro-text">Yay! Supports javascript</p>
person avi    schedule 18.10.2014
comment
К сожалению, нет поддержки Windows. - person Expenzor; 17.04.2017
comment
Есть ли альтернативы для тех, кто занимается программированием в Windows? - person Hoshiko86; 05.06.2017
comment
@Expenzor Я работаю над окнами. PhantomJS отлично работает. - person Aakash Choubey; 12.01.2018
comment
Стоит отметить, что поддержка PhantomJS прекращена, и он больше не находится в активной разработке в свете того, что Chrome теперь поддерживает Headless. Предлагается использование безголового Chrome / Firefox. - person sytech; 23.03.2018
comment
@sytech это? Я вижу регулярные коммиты - github.com/ariya/phantomjs/commit/master - person avi; 29.03.2018
comment
Я получаю следующее предупреждение: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead. Может быть, @sytech говорил о поддержке Selenium для него? - person jpmc26; 30.04.2018
comment
Это и поддержка селена, и сам PhantomJS. github.com/ariya/phantomjs/issues/15344 - person sytech; 30.04.2018
comment
Этот ответ тоже устарел в 2021 году ... - person IceFire; 14.02.2021

Мы не получаем правильных результатов, потому что любой контент, сгенерированный javascript, должен отображаться в DOM. Когда мы получаем HTML-страницу, мы получаем исходную, не измененную javascript, DOM.

Поэтому нам нужно отобразить содержимое javascript перед сканированием страницы.

Поскольку селен уже много раз упоминается в этой ветке (и иногда упоминалось также, как медленно он становится), я перечислю два других возможных решения.


Решение 1. Это очень хорошее руководство по как использовать Scrapy для сканирования сгенерированного javascript content, и мы будем следить именно за этим.

Что нам понадобится:

  1. На нашем компьютере установлен Docker. Это преимущество перед другими решениями до этого момента, поскольку оно использует платформу, не зависящую от ОС.

  2. # P7 #
    # P8 #
    # P9 #
  3. Запустите сервер заставки: sudo docker run -p 8050:8050 scrapinghub/splash.

  4. Установите плагин scrapy-splash: pip install scrapy-splash

  5. # P12 #
    # P13 #
    DOWNLOADER_MIDDLEWARES = {
          'scrapy_splash.SplashCookiesMiddleware': 723,
          'scrapy_splash.SplashMiddleware': 725,
          'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
    }
    
    # P14 #
    SPLASH_URL = 'http://localhost:8050'
    
    # P15 #
    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    
  6. # P16 #
    # P17 #
    class MySpider(scrapy.Spider):
        name = "jsscraper"
        start_urls = ["http://quotes.toscrape.com/js/"]
    
        def start_requests(self):
            for url in self.start_urls:
            yield SplashRequest(
                url=url, callback=self.parse, endpoint='render.html'
            )
    
        def parse(self, response):
            for q in response.css("div.quote"):
            quote = QuoteItem()
            quote["author"] = q.css(".author::text").extract_first()
            quote["quote"] = q.css(".text::text").extract_first()
            yield quote
    
    # P18 #

Решение 2: назовем это экспериментальным на данный момент (май 2018 г.) ...
Это решение предназначено только для версии Python 3.6 (на данный момент).

Знаете ли вы модуль запросы (а кто не знает)?
Сейчас у него есть маленький брат, сканирующий Интернет: requests-HTML:

Эта библиотека призвана сделать синтаксический анализ HTML (например, сканирование Интернета) максимально простым и интуитивно понятным.

  1. Установить запросы-html: pipenv install requests-html

  2. Сделайте запрос на адрес страницы:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
    
  3. Визуализируйте ответ, чтобы получить биты, сгенерированные Javascript:

    r.html.render()
    

Наконец, модуль, похоже, предлагает возможности парсинга.
В качестве альтернативы мы можем попробовать хорошо задокументированный способ использования BeautifulSoup с только что визуализированным объектом r.html.

person John Moutafis    schedule 30.05.2018
comment
Можете ли вы подробнее рассказать о том, как получить полный HTML-контент с загруженными битами JS после вызова .render ()? Я застрял после этого момента. Я не вижу всех окон iframe, которые обычно вводятся на страницу из JavaScript в объекте r.html.html. - person anon58192932; 13.12.2018
comment
@ anon58192932 Поскольку на данный момент это экспериментальное решение, и я не знаю, чего именно вы пытаетесь достичь в результате, я не могу ничего предложить ... Вы можете создать новый вопрос здесь, на SO, если у вас нет разработал решение еще - person John Moutafis; 02.01.2019
comment
Я получил эту ошибку: RuntimeError: невозможно использовать HTMLSession в существующем цикле событий. Вместо этого используйте AsyncHTMLSession. - person Joshua Stafford; 23.04.2019
comment
@HuckIt, похоже, это известная проблема: github.com/psf/requests-html/ вопросов / 140 - person John Moutafis; 15.10.2019

Может быть, selenium сможет это сделать.

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source
person amazingthere    schedule 14.04.2016
comment
Selenium действительно тяжел для такого рода вещей, это было бы излишне медленным и потребовало бы головы браузера, если вы не используете PhantomJS, но это сработает. - person Joshua Hedges; 28.07.2017
comment
@JoshuaHedges Вы можете запускать другие, более стандартные браузеры в автономном режиме. - person reynoldsnlp; 09.01.2020
comment
@fantabolous - хотя я использовал эти параметры, окно браузера все равно открывается - person Zvi; 09.01.2021
comment
@fantabolous - понял свою ошибку. В настоящее время работает - person Zvi; 09.01.2021

Если вы когда-либо использовали модуль Requests для python раньше, я недавно узнал, что разработчик создал новый модуль с именем Requests-HTML, который теперь также может отображать JavaScript.

Вы также можете посетить https://html.python-requests.org/, чтобы узнать больше об этом модуле. , или если вас интересует только рендеринг JavaScript, вы можете посетить https://html.python-requests.org/?#javascript-support, чтобы напрямую узнать, как использовать модуль для рендеринга JavaScript с помощью Python.

По сути, после правильной установки модуля Requests-HTML следующий пример, который показан по указанной выше ссылке, показывает, как вы можете использовать этот модуль для очистки веб-сайта и визуализации кода JavaScript, содержащегося на веб-сайте:

from requests_html import HTMLSession
session = HTMLSession()

r = session.get('http://python-requests.org/')

r.html.render()

r.html.search('Python 2 will retire in only {months} months!')['months']

'<time>25</time>' #This is the result.

Я недавно узнал об этом из видео на YouTube. Нажмите здесь!, чтобы посмотреть видео YouTube, демонстрирующее, как работает модуль.

person SShah    schedule 16.04.2018
comment
Следует отметить, что этот модуль поддерживает только Python 3.6. - person nat5142; 12.10.2018
comment
Я получил эту ошибку: SSLError: HTTPSConnectionPool (host = 'docs.python-requests.org', port = 443): Превышено максимальное количество повторных попыток с URL: / (Вызвано SSLError (SSLError (1, '[SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert внутренняя ошибка (_ssl.c: 1045) '))) - person Joshua Stafford; 23.04.2019
comment
@HuckIt приношу свои извинения, я не знаком с этой ошибкой, однако похоже, что на веб-сайте, на который вы пытались перейти, могла быть проблема, связанная с сертификатом SSL. Извините, это не решение, но я бы порекомендовал вам задать новый вопрос здесь, в переполнении стека (если он еще не был задан) и, возможно, предоставить более подробную информацию, например URL-адрес веб-сайта, который вы использовали, и ваш код. - person SShah; 28.04.2019
comment
Кажется, под капотом используется хром. Хотя отлично работает для меня - person Sid; 27.04.2020

Похоже, что к данным, которые вы действительно ищете, можно получить доступ через вторичный URL-адрес, который вызывается некоторым javascript на первичной странице.

Хотя вы можете попробовать запустить javascript на сервере, чтобы справиться с этим, более простым подходом может быть загрузка страницы с помощью Firefox и использование такого инструмента, как

comment
@Kris На случай, если кто-то наткнется на это и захочет попробовать вместо чего-то тяжелого, например, селен, вот небольшой пример. Это откроет страницу сведений о детали для шестигранной гайки на веб-сайте McMaster-Carr. Контент их веб-сайтов в основном загружается с использованием Javascript и содержит очень мало информации о нативной странице. Если вы откроете инструменты разработчика в своем браузере, перейдете на вкладку «Сеть» и обновите страницу, вы сможете увидеть все запросы, сделанные страницей, и найти соответствующие данные (в данном случае - подробный HTML-код). - person SweepingsDemon; 13.08.2018
comment
Это другой URL, найденный в Вкладка Firefox devtool Network, которая, если следовать, содержит html для большей части информации о деталях и предоставляет некоторые параметры, необходимые для легкого перехода к информации о других деталях для облегчения очистки. Этот конкретный пример не особенно полезен, поскольку цена генерируется другой функцией Javascript, но он должен служить достаточно хорошим введением для всех, кто хочет последовать совету Стивена. - person SweepingsDemon; 13.08.2018

Это также кажется хорошим решением, взятым из отличное сообщение в блоге

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *  
from lxml import html 

#Take this class for granted.Just use result of rendering.
class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)  
    self.loadFinished.connect(self._loadFinished)  
    self.mainFrame().load(QUrl(url))  
    self.app.exec_()  

  def _loadFinished(self, result):  
    self.frame = self.mainFrame()  
    self.app.quit()  

url = 'http://pycoders.com/archive/'  
r = Render(url)  
result = r.frame.toHtml()
# This step is important.Converting QString to Ascii for lxml to process

# The following returns an lxml element tree
archive_links = html.fromstring(str(result.toAscii()))
print archive_links

# The following returns an array containing the URLs
raw_links = archive_links.xpath('//div[@class="campaign"]/a/@href')
print raw_links
person marbel    schedule 10.04.2016

Selenium лучше всего подходит для очистки содержимого JS и Ajax.

Ознакомьтесь с этой статьей, чтобы узнать об извлечении данных из Интернета с помощью Python

$ pip install selenium

Затем загрузите веб-драйвер Chrome.

from selenium import webdriver

browser = webdriver.Chrome()

browser.get("https://www.python.org/")

nav = browser.find_element_by_id("mainnav")

print(nav.text)

Легко, правда?

person Macnux    schedule 18.01.2018
comment
селен лучше всего, но некоторые сайты, похоже, обнаруживают использование селена. - person Jawad Ahmad Khan; 26.08.2020

Лично я предпочитаю использовать scrapy и selenium и докерировать их в отдельных контейнерах. Таким образом, вы можете установить как с минимальными трудностями, так и сканировать современные веб-сайты, которые почти все содержат javascript в той или иной форме. Вот пример:

Используйте scrapy startproject, чтобы создать скребок и написать своего паука, скелет может быть таким простым:

import scrapy


class MySpider(scrapy.Spider):
    name = 'my_spider'
    start_urls = ['https://somewhere.com']

    def start_requests(self):
        yield scrapy.Request(url=self.start_urls[0])


    def parse(self, response):

        # do stuff with results, scrape items etc.
        # now were just checking everything worked

        print(response.body)

Настоящая магия происходит в middlewares.py. Перезаписать два метода в промежуточном программном обеспечении загрузчика, __init__ и process_request, следующим образом:

# import some additional modules that we need
import os
from copy import deepcopy
from time import sleep

from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver

class SampleProjectDownloaderMiddleware(object):

def __init__(self):
    SELENIUM_LOCATION = os.environ.get('SELENIUM_LOCATION', 'NOT_HERE')
    SELENIUM_URL = f'http://{SELENIUM_LOCATION}:4444/wd/hub'
    chrome_options = webdriver.ChromeOptions()

    # chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)
    self.driver = webdriver.Remote(command_executor=SELENIUM_URL,
                                   desired_capabilities=chrome_options.to_capabilities())


def process_request(self, request, spider):

    self.driver.get(request.url)

    # sleep a bit so the page has time to load
    # or monitor items on page to continue as soon as page ready
    sleep(4)

    # if you need to manipulate the page content like clicking and scrolling, you do it here
    # self.driver.find_element_by_css_selector('.my-class').click()

    # you only need the now properly and completely rendered html from your page to get results
    body = deepcopy(self.driver.page_source)

    # copy the current url in case of redirects
    url = deepcopy(self.driver.current_url)

    return HtmlResponse(url, body=body, encoding='utf-8', request=request)

Не забудьте включить это промежуточное ПО, раскомментировав следующие строки в файле settings.py:

DOWNLOADER_MIDDLEWARES = {
'sample_project.middlewares.SampleProjectDownloaderMiddleware': 543,}

Далее для докеризации. Создайте свой Dockerfile из легкого образа (здесь я использую python Alpine), скопируйте в него каталог вашего проекта, установите требования:

# Use an official Python runtime as a parent image
FROM python:3.6-alpine

# install some packages necessary to scrapy and then curl because it's  handy for debugging
RUN apk --update add linux-headers libffi-dev openssl-dev build-base libxslt-dev libxml2-dev curl python-dev

WORKDIR /my_scraper

ADD requirements.txt /my_scraper/

RUN pip install -r requirements.txt

ADD . /scrapers

И, наконец, объединим все это в docker-compose.yaml:

version: '2'
services:
  selenium:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"
    shm_size: 1G

  my_scraper:
    build: .
    depends_on:
      - "selenium"
    environment:
      - SELENIUM_LOCATION=samplecrawler_selenium_1
    volumes:
      - .:/my_scraper
    # use this command to keep the container running
    command: tail -f /dev/null

Запустите docker-compose up -d. Если вы делаете это в первый раз, ему потребуется некоторое время, чтобы получить последнюю версию selenium / standalone-chrome, а также создать образ скребка.

Как только это будет сделано, вы можете проверить, что ваши контейнеры работают с docker ps, а также убедиться, что имя контейнера селена совпадает с именем переменной среды, которую мы передали нашему контейнеру-скребку (здесь это было SELENIUM_LOCATION=samplecrawler_selenium_1).

Введите свой контейнер для парсера с помощью docker exec -ti YOUR_CONTAINER_NAME sh, для меня команда была docker exec -ti samplecrawler_my_scraper_1 sh, перейдите в правильный каталог и запустите парсер с помощью scrapy crawl my_spider.

Вся эта информация находится на моей странице github, и вы можете получить ее здесь

person tarikki    schedule 30.05.2018

Вы также можете выполнить javascript с помощью webdriver.

from selenium import webdriver

driver = webdriver.Firefox()
driver.get(url)
driver.execute_script('document.title')

или сохраните значение в переменной

result = driver.execute_script('var text = document.title ; return text')
person Serpentr    schedule 28.03.2017
comment
или вы можете просто использовать свойство driver.title - person Corey Goldberg; 18.06.2018

Мне очень хорошо подходит сочетание BeautifulSoup и Selenium.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup as bs

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
    try:
        element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))) #waits 10 seconds until element is located. Can have other wait conditions  such as visibility_of_element_located or text_to_be_present_in_element

        html = driver.page_source
        soup = bs(html, "lxml")
        dynamic_text = soup.find_all("p", {"class":"class_name"}) #or other attributes, optional
    else:
        print("Couldnt locate element")

P.S. Дополнительные условия ожидания можно найти здесь

person Biarys    schedule 29.05.2018

Использование PyQt5

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


class Client(QWebEnginePage):
    def __init__(self,url):
        global app
        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,data):
        self.html = data
        self.app.quit()

# url = ""
# client_response = Client(url)
# print(client_response.html)
person Ash-Ishh..    schedule 14.07.2018
comment
+1, спасибо! Это было решение, которое сработало для меня, поскольку селен немного избыточен для такой простой задачи, а request-html предназначен только для python 3.6. Я бы рекомендовал это решение по сравнению с любым другим. - person WhiteWood; 01.06.2021

Вы захотите использовать веб-драйвер urllib, requests, beautifulSoup и selenium в своем скрипте для разных частей страницы (и это лишь некоторые из них).
Иногда вы получите то, что вам нужно, всего лишь с одним из этих модулей.
Иногда вам понадобятся два, три или все эти модули.
Иногда вам нужно отключить js в вашем браузере.
Иногда вам понадобится информация заголовка в вашем скрипте. < br> Ни один веб-сайт не может быть очищен таким же образом, и ни один веб-сайт не может быть очищен таким же образом навсегда без необходимости изменения вашего сканера, обычно через несколько месяцев. Но их все можно поскрести! Там, где есть желание, есть и способ наверняка.
Если вам нужно постоянно собирать данные в будущем, просто очистите все, что вам нужно, и сохраните их в файлах .dat с помощью pickle.
Просто продолжайте искать, как попробовать что с этими модулями и копирование и вставка ваших ошибок в Google.

person Community    schedule 28.03.2017

Как уже упоминалось, Selenium - хороший выбор для рендеринга результатов JavaScript:

from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options

options = Options()
options.headless = True
browser = Firefox(executable_path="/usr/local/bin/geckodriver", options=options)

url = "https://www.example.com"
browser.get(url)

А gazpacho - действительно простая библиотека для синтаксического анализа визуализированного HTML:

from gazpacho import Soup

soup = Soup(browser.page_source)
soup.find("a").attrs['href']
person emehex    schedule 09.10.2020

Недавно я использовал библиотеку requests_html для решения этой проблемы.

Их расширенная документация на readthedocs.io довольно хороша (пропустите аннотированная версия на pypi.org). Если ваш вариант использования является базовым, вы, скорее всего, добьетесь успеха.

from requests_html import HTMLSession
session = HTMLSession()
response = session.request(method="get",url="www.google.com/")
response.html.render()

Если у вас возникли проблемы с рендерингом необходимых данных с помощью response.html.render (), вы можете передать некоторый javascript в функцию рендеринга для рендеринга конкретного объекта js, который вам нужен. Это скопировано из их документации, но это может быть именно то, что вам нужно:

Если указан сценарий, он будет выполнять предоставленный JavaScript во время выполнения. Пример:

script = """
    () => {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            deviceScaleFactor: window.devicePixelRatio,
        }
    } 
"""

Возвращает возвращаемое значение выполненного скрипта, если таковое предусмотрено:

>>> response.html.render(script=script)
{'width': 800, 'height': 600, 'deviceScaleFactor': 1}

В моем случае данные, которые мне нужны, были массивами, которые заполняли график javascript, но данные не отображались в виде текста нигде в html. Иногда вообще не ясно, какие имена объектов у данных, которые вы хотите, если данные заполняются динамически. Если вы не можете отследить js-объекты непосредственно из источника представления или проверить, вы можете ввести window, а затем нажать ENTER в консоли отладчика в браузере (Chrome), чтобы вывести полный список объектов, отображаемых браузером. Если вы сделаете несколько обоснованных предположений о том, где хранятся данные, возможно, вам повезет найти их там. Мои данные графика находились в консоли window.view.data, поэтому в переменной скрипта, переданной в метод .render (), указанный выше, я использовал:

return {
    data: window.view.data
}
person Digestible1010101    schedule 30.12.2020
comment
Похоже, что requests_html больше не поддерживается (последнее обновление май 2020 г.). Он использует pyppeteer для рендеринга, который, похоже, активно поддерживается; он использует Chromium для рендеринга внизу. - person VirtualScooter; 05.07.2021

Pyppeteer

Вы можете рассмотреть Pyppeteer, порт Python для интерфейса драйвера Chrome / Chromium Кукловод.

Вот простой пример, показывающий, как вы можете использовать Pyppeteer для доступа к данным, которые были динамически введены на страницу:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch({"headless": True})
    [page] = await browser.pages()

    # normally, you go to a live site...
    #await page.goto("http://www.example.com")
    # but for this example, just set the HTML directly:
    await page.setContent("""
    <body>
    <script>
    // inject content dynamically with JS, not part of the static HTML!
    document.body.innerHTML = `<p>hello world</p>`; 
    </script>
    </body>
    """)
    print(await page.content()) # shows that the `<p>` was inserted

    # evaluate a JS expression in browser context and scrape the data
    expr = "document.querySelector('p').textContent"
    print(await page.evaluate(expr, force_expr=True)) # => hello world

    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

См. справочную документацию по Pyppeteer.

person ggorlen    schedule 30.03.2021

Попробуйте получить доступ к API напрямую

Обычный сценарий, который вы увидите при парсинге, заключается в том, что данные запрашиваются асинхронно из конечной точки API веб-страницей. Минимальным примером этого может быть следующий сайт:

<body>
<script>
fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then(res => {
    if (!res.ok) throw Error(res.status);
    
    return res.json();
  })
  .then(data => {
    // inject data dynamically via JS after page load
    document.body.innerText = data.title;
  })
  .catch(err => console.error(err))
;
</script>
</body>

Во многих случаях API будет защищен CORS или токеном доступа или будет чрезмерно ограничен по скорости, но в других случаях он общедоступен, и вы можете полностью обойти веб-сайт. При проблемах с CORS вы можете попробовать cors - где угодно.

Общая процедура заключается в использовании вкладки сети инструментов разработчика вашего браузера для поиска запросов, сделанных страницей, по ключевым словам / подстрокам данных, которые вы хотите очистить. Часто вы увидите незащищенную конечную точку запроса API с полезной нагрузкой JSON, к которой вы можете получить доступ напрямую с помощью модулей urllib или requests. Так обстоит дело с приведенным выше исполняемым фрагментом, который вы можете использовать на практике. После нажатия на кнопку «Выполнить фрагмент» я нашел конечную точку на вкладке «Сеть» следующим образом:

пример вкладки сети, показывающей конечную точку удаленного URL, найденную при поиске

Этот пример надуман; URL-адрес конечной точки, вероятно, будет неочевиден при взгляде на статическую разметку, потому что он может быть динамически собран, уменьшен и скрыт под десятками других запросов и конечных точек. В сетевом запросе также будут показаны все соответствующие данные о полезной нагрузке запроса, например токен доступа, который может вам понадобиться.

После получения URL-адреса конечной точки и соответствующей информации создайте запрос в Python, используя стандартную библиотеку HTTP, и запросите данные:

>>> import requests
>>> res = requests.get("https://jsonplaceholder.typicode.com/posts/1")
>>> data = res.json()
>>> data["title"]
'sunt aut facere repellat provident occaecati excepturi optio reprehenderit'

Когда это может сойти с рук, это, как правило, намного проще, быстрее и надежнее, чем очистка страницы с помощью Selenium, Pyppeteer, Scrapy или любых других популярных библиотек парсинга в то время, когда вы читаете этот пост.

Если вам не повезло и данные не были доставлены через запрос API, который возвращает данные в удобном формате, они могут быть частью полезной нагрузки исходного браузера в теге <script>, либо в виде строки JSON, либо (что более вероятно) JS объект. Например:

<body>
<script>
  var someHardcodedData = {
    userId: 1,
    id: 1,
    title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 
    body: 'quia et suscipit\nsuscipit recusandae con sequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'
  };
  document.body.textContent = someHardcodedData.title;
</script>
</body>

Не существует универсального способа получения этих данных. Основной метод - использовать BeautifulSoup для доступа к тексту тега <script>, а затем применить регулярное выражение или синтаксический анализ для извлечения структуры объекта, строки JSON или любого другого формата, в котором могут быть данные. Вот доказательство концепции структуры образца показано выше:

import json
import re
from bs4 import BeautifulSoup

# pretend we've already used requests to retrieve the data, 
# so we hardcode it for the purposes of this example
text = """
<body>
<script>
  var someHardcodedData = {
    userId: 1,
    id: 1,
    title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 
    body: 'quia et suscipit\nsuscipit recusandae con sequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'
  };
  document.body.textContent = someHardcodedData.title;
</script>
</body>
"""
soup = BeautifulSoup(text, "lxml")
script_text = str(soup.select_one("script"))
pattern = r"title: '(.*?)'"
print(re.search(pattern, script_text, re.S).group(1))

Ознакомьтесь с этими ресурсами для анализа объектов JS, которые не совсем корректны JSON:

Вот несколько дополнительных тематических исследований / доказательств концепции, в которых парсинг был обойден с помощью API:

Если ничего не помогает, попробуйте одну из многих библиотек динамического парсинга, перечисленных в этом потоке.

person ggorlen    schedule 30.03.2021

Простое и быстрое решение:

У меня была такая же проблема. Я хочу очистить некоторые данные, созданные с помощью JavaScript. Если я соскребу только текст с этого сайта с помощью BeautifulSoup, то я закончу с тегами в тексте. Я хочу отобразить этот тег и хочу извлечь из него информацию. Кроме того, я не хочу использовать тяжелые фреймворки, такие как Scrapy и selenium.

Итак, я обнаружил, что метод запросов get module принимает URL-адреса и фактически отображает тег скрипта.

Пример:

import requests
custom_User_agent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
url = "https://www.abc.xyz/your/url"
response = requests.get(url, headers={"User-Agent": custom_User_agent})
html_text = response.text

Это отобразит загруженный сайт и отобразит теги.

Надеюсь, это поможет как быстрое и простое решение для рендеринга сайта, загруженного тегами скриптов.

person HITESH GUPTA    schedule 16.06.2021
comment
не могли бы вы включить в свой ответ пример веб-сайта, который обеспечивает рендеринг тегов скрипта? - person VirtualScooter; 05.07.2021