Веб-соскабливание страницы JavaScript с Python

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

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

response = urllib2.urlopen(request)

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

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

Ответ 1

EDIT 30/Dec/2017: Этот ответ появляется в результатах поиска Google, поэтому я решил его обновить. Старый ответ все еще в конце.

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

После установки Phantom JS убедитесь, что phantomjs файл 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 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 для очистки веб-сайтов, управляемых 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>

Ответ 2

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

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

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


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

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

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

  2. Установите Splash, следуя инструкциям, приведенным для нашей соответствующей ОС.
    Цитирование из заставки:

    Splash - это сервис рендеринга JavaScript. Это легкий веб-браузер с HTTP API, реализованный в Python 3 с использованием Twisted и QT5.

    По сути, мы собираемся использовать Splash для визуализации сгенерированного Javascript контента.

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

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

  5. Предполагая, что у нас уже есть проект Scrapy (если нет, , давайте создадим один), мы будем следовать руководству и обновим settings.py:

    Затем перейдите к своим проектам Scrapy settings.py и установите эти промежуточные программы:

    DOWNLOADER_MIDDLEWARES = {
          'scrapy_splash.SplashCookiesMiddleware': 723,
          'scrapy_splash.SplashMiddleware': 725,
          'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
    }
    

    URL-адрес сервера Splash (если вы используете Win или OSX, это должен быть URL-адрес докера: Как получить IP-адрес контейнера Docker с хоста?):

    SPLASH_URL = 'http://localhost:8050'
    

    И, наконец, вам нужно также установить эти значения:

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    
  6. Наконец, мы можем использовать SplashRequest:

    У обычного паука у вас есть объекты Request, которые вы можете использовать для открытия URL. Если страница, которую вы хотите открыть, содержит данные, сгенерированные JS, вы должны использовать SplashRequest (или SplashFormRequest) для отображения страницы. Вот простой пример:

    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
    

    SplashRequest отображает URL-адрес в виде HTML и возвращает ответ, который можно использовать в методе обратного вызова (синтаксический анализ).


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

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

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

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

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

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

    r.html.render()
    

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

Ответ 3

Возможно, selenium может это сделать.

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source

Ответ 4

Если вы когда-либо использовали модуль 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, демонстрирующий работу модуля.

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

Селен лучше всего подходит для очистки содержимого 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)

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

Ответ 8

Вы также можете выполнить 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 var')

Ответ 9

Лично я предпочитаю использовать скрап и 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  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. Если вы делаете это в первый раз, потребуется некоторое время, чтобы он извлек последнюю версию селен/автономный хром, а также построил ваш скребковый образ.

Как только это будет сделано, вы можете проверить, что ваши контейнеры работают с docker ps, а также убедиться, что имя контейнера selenium совпадает с именем переменной среды, которую мы передали нашему контейнеру скребка (здесь это было 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, и вы можете получить его здесь here

Ответ 10

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

Ответ 11

Сочетание 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. Вы можете найти больше условий ожидания здесь

Ответ 12

Использование 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)