Веб-скреблінг сторінки JavaScript з Python


178

Я намагаюся розробити простий веб-скребок. Я хочу витягнути текст без HTML-коду. Насправді я досягаю цієї мети, але я бачив, що на деяких сторінках, де завантажений JavaScript, я не отримав хороших результатів.

Наприклад, якщо якийсь код JavaScript додає якийсь текст, я його не бачу, тому що коли я телефоную

response = urllib2.urlopen(request)

Я отримую оригінальний текст без доданого (оскільки JavaScript виконується в клієнті).

Отже, я шукаю кілька ідей, щоб вирішити цю проблему.


2
Здається, вам може знадобитися щось важче, спробуйте Селен або Ватір.
Вім

2
Я успішно зробив це в 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
bpgergo

Відповіді:


203

РЕДАКЦІЯ 30 / груд / 2017: ця відповідь відображається у найкращих результатах пошуку Google, тому я вирішив її оновити. Стара відповідь ще в кінці.

dryscape більше не підтримується, і розробники бібліотеки dryscape рекомендують лише Python 2. Я знайшов використання бібліотеки пітонів 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, щоб скребти веб-сайти, керовані 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>

16
На жаль, немає підтримки Windows.
Expenzor

1
Будь-які альтернативи для тих, хто програмує в Windows?
Hoshiko86

2
@ExpenzorЯ працюю на вікнах. PhantomJS працює чудово.
Аакаш Чубі

17
Варто зауважити, що PhantomJS було припинено і більше не перебуває під активною розробкою в світлі Chrome, що тепер підтримує голову. Запропоновано використання безголового хрому / фаєрфоксу.
sytech

3
Це як підтримка селену, так і сам PhantomJS. github.com/ariya/phantomjs/isissue/15344
sytech

73

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

Тому нам потрібно відобразити вміст javascript, перш ніж сканувати сторінку.

Оскільки в цій нитці вже багато разів згадується про селен (і про те, як повільно він стає іноді), я перерахую ще два можливі рішення.


Рішення 1: Це дуже хороший підручник щодо використання Scrap для сканування вмісту, створеного JavaScript, і ми будемо слідувати саме цьому.

Що нам знадобиться:

  1. Докер встановлений в нашій машині. Це плюс до інших рішень до цього моменту, оскільки він використовує незалежну від ОС платформу.

  2. Встановіть Splash, дотримуючись інструкцій, наведених для нашої відповідної ОС.
    Цитування з документації за сплеск:

    Splash - це послуга надання JavaScript. Це легкий веб-браузер із HTTP API, реалізований у Python 3 за допомогою Twisted та QT5.

    По суті, ми будемо використовувати Splash для відтворення вмісту, створеного Javascript.

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

  4. Встановіть плагін scrap-splash :pip install scrapy-splash

  5. Припускаючи, що у нас вже створений проект Scrap (якщо ні, давайте зробимо його ), ми будемо слідувати керівництву та оновити settings.py:

    Потім перейдіть до проекту скрапінгу 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:

    У звичайного павука є запит об'єктів, які ви можете використовувати для відкриття 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 (на даний момент).

Чи знаєте ви модуль запитів (добре хто ні)?
Тепер у нього є веб-сканування маленького побратима: request-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об'єктом, який ми тільки що надали.


Ви можете розширити, як отримати повний HTML-вміст із завантаженими бітами JS після виклику .render ()? Я затримався після цього моменту. Я не бачу всіх r.html.htmlоб'єктів iframe, які зазвичай вводяться в сторінку з JavaScript в об'єкті.
anon58192932

@ anon58192932 Оскільки на даний момент це експериментальне рішення, і я не знаю, чого саме ви намагаєтеся досягти в результаті, я не можу нічого запропонувати ... Ви можете створити нове запитання тут, так як у вас немає ще розробив рішення
Джон Мутафіс

2
Я отримав цю помилку: RuntimeError: Неможливо використовувати HTMLSession у межах існуючого циклу подій. Використовуйте замість AsyncHTMLSession.
HuckIt

1
@HuckIz здається, це відоме питання: github.com/psf/requests-html/isissue/140
Джон Мутафіс

47

Можливо, селен може це зробити.

from selenium import webdriver
import time

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

3
Селен дуже важкий для подібних речей, що буде надмірно повільним і вимагає голови браузера, якщо ви не використовуєте PhantomJS, але це спрацює.
Джошуа Хеджес

@JoshuaHedges Ви можете запускати інші більш стандартні браузери в режимі без голови.
reynoldsnlp

22

Якщо ви коли-небудь раніше використовували Requestsмодуль для python, я нещодавно з’ясував, що розробник створив новий модуль під назвою, Requests-HTMLякий тепер також має можливість візуалізації JavaScript.

Ви також можете відвідати https://html.python-requests.org/, щоб дізнатись більше про цей модуль, або якщо вас цікавить лише надання JavaScript, ви можете відвідати https://html.python-requests.org/?#javascript -підтримка безпосередньо дізнатися, як використовувати модуль для візуалізації 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, де показано, як працює модуль.


3
Слід зазначити, що цей модуль підтримує лише Python 3.6.
nat5142

1
Я отримав цю помилку: SSLError: HTTPSConnectionPool (host = 'docs.python-requests.org', port = 443): Максимум спроб перевищено за допомогою url: / (викликано SSLError (SSLError (1, '[SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 попередження внутрішня помилка (_ssl.c: 1045) ')))
HuckIt

@HuckIt appologies Я не знайомий з цією помилкою, однак помилка, здається, на веб-сайті, до якого ви намагалися зайти, можливо, виникли проблеми, пов'язані із сертифікацією SSL. Вибачте, це не є рішенням, але я рекомендую вам поставити нове запитання, тут переповнюється стек (якщо його вже не було запитано) та, можливо, надати більше детальних відомостей, таких як URL-адресу веб-сайту, який ви використовували, та свій код.
SShah

Здається, використовується хром під кришкою. Для мене це чудово працює
Sid

14

Здається, це також є гарним рішенням, взятим із чудової публікації в блозі

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

12

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

Хоча ви можете спробувати запустити JavaScript на сервері, щоб впоратися з цим, більш простим підходом може бути завантаження сторінки за допомогою Firefox і використання інструменту, як Чарльз або Firebug, щоб точно визначити, що ця вторинна URL-адреса. Тоді ви можете просто запитати цю URL-адресу безпосередньо для даних, які вас цікавлять.


@Kris На всякий випадок, коли хтось наткнеться на це і захоче спробувати це замість чогось такого важкого, як селен, ось короткий приклад. Це відкриє сторінку деталей деталі для шестигранної гайки на веб-сайті McMaster-Carr. Вміст їх веб-сайту в основному вибирається за допомогою Javascript і має дуже мало інформації про рідну сторінку. Якщо ви відкриєте інструменти для розробників веб-переглядача, перейдіть на вкладку «Мережа» та оновіть сторінку, ви зможете побачити всі запити, зроблені сторінкою, та знайти відповідні дані (у цьому випадку деталі html).
ПідмітанняДемон

Це інша URL-адреса, знайдена на вкладці Мережі Firefox devtool Network, яка, якщо дотримується, містить html для більшої частини інформації про деталі та розкриває деякі параметри, необхідні для легкого переходу до іншої інформації про деталі для легшого скребкування. Цей конкретний приклад не особливо корисний, оскільки ціна генерується іншою функцією Javascript, але повинна слугувати досить добре, як вступ для всіх, хто хоче слідувати порадам Стівена.
ПідмітанняДемон

12

Селен найкращий для вискоблювання вмісту 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')

або ви можете просто скористатись driver.titleоб'єктом нерухомості
Corey Goldberg

8

Я особисто вважаю за краще використовувати скрап та селен та докерінг в обох контейнерах. Таким чином ви можете встановити як мінімум клопоту, так і сканувати сучасні веб-сайти, які майже всі містять 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)

Справжня магія трапляється у midwares.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. Якщо ви робите це вперше, знадобиться певний час, щоб отримати найсвіжіший селен / автономний хром і створити також ваше скребкове зображення.

Як тільки це буде зроблено, ви можете перевірити, чи працюють ваші контейнери, 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, і ви можете отримати її звідси


5

Суміш 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")

PS Ви можете знайти більше умов очікування тут


4

Ви хочете використовувати у своєму скрипті URL-адрес, запити, веб-драйвер beautifulSoup та селен для різних частин сторінки (щоб назвати декілька).
Іноді ви отримаєте те, що вам потрібно, лише за допомогою одного з цих модулів.
Іноді вам знадобляться два, три або всі ці модулі.
Іноді вам потрібно буде вимкнути js у своєму браузері.
Іноді вам потрібна інформація заголовка у вашому сценарії.
Жоден веб-сайт не може бути скреблений однаковим чином, і жоден веб-сайт не може бути зібраний таким же чином назавжди без необхідності змінювати вашому сканеру, як правило, через кілька місяців. Але їх усіх можна зішкріб! Там, де є воля, є спосіб точно.
Якщо вам потрібні постійні скребки даних у майбутньому, просто скребте все необхідне та зберігайте їх у .dat-файлах із солінням.
Просто продовжуйте шукати, як спробувати, що з цими модулями, і скопіюйте та вставте свої помилки в Google.


3

Використання 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)

1

Я вже два дні намагаюся знайти відповідь на це питання. Багато відповідей направляють вас на різні питання. Але відповідь змія, подана вище, дійсно до речі. Це найкоротше, найпростіше рішення. Просто нагадування останнє слово "var" представляє ім'я змінної , тому її слід використовувати як:

 result = driver.execute_script('var text = document.title ; return text')

Це має бути коментар до відповіді змія, а не окремою відповіддю.
Yserbius

1
Це очевидно. Але у мене ще немає 50 повторень, щоб коментувати чужу відповідь.
Abd_bgc

0

Мені довелося зіткнутися з цією ж проблемою в деяких власних веб-проектах зі скребки. Як я справився з цим, використовуючи бібліотеку запитів python, щоб зробити запит http безпосередньо в API, замість того, щоб завантажувати JS.

Для цього добре працює бібліотека запитів python, і ви можете бачити http-запити, використовуючи елемент перевірки та перейшовши на вкладку мережі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.