Pagina JavaScript di Web scraping con Python


178

Sto cercando di sviluppare un semplice raschietto web. Voglio estrarre il testo senza il codice HTML. In effetti, ho raggiunto questo obiettivo, ma ho visto che in alcune pagine in cui è caricato JavaScript non ho ottenuto buoni risultati.

Ad esempio, se un codice JavaScript aggiunge del testo, non riesco a vederlo, perché quando chiamo

response = urllib2.urlopen(request)

Ottengo il testo originale senza quello aggiunto (perché JavaScript viene eseguito nel client).

Quindi, sto cercando alcune idee per risolvere questo problema.


2
Sembra che potresti aver bisogno di qualcosa di più pesante, prova Selenium o Watir.
mercoledì

2
L'ho fatto con successo in Java (ho usato il toolkit Cobra lobobrowser.org/cobra.jsp ) Dato che vuoi hackerare in Python (sempre una buona scelta) ti consiglio queste due opzioni: - packtpub.com/article/ web-scraping-with-python-part-2 - blog.databigbang.com/web-scraping-ajax-and-javascript-sites
bpgergo

Risposte:


203

EDIT 30 / Dec / 2017: questa risposta appare nei migliori risultati delle ricerche di Google, quindi ho deciso di aggiornarla. La vecchia risposta è ancora alla fine.

dryscape non è più gestito e la libreria consigliata dagli sviluppatori di dryscape è solo Python 2. Ho scoperto di utilizzare la libreria Python di Selenium con Phantom JS come driver Web abbastanza veloce e facile da eseguire.

Dopo aver installato Phantom JS , assicurati che il phantomjsbinario sia disponibile nel percorso corrente:

phantomjs --version
# result:
2.1.1

Esempio

Per fare un esempio, ho creato una pagina di esempio con il seguente codice HTML. ( link ):

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

senza javascript dice: No javascript supporte con javascript:Yay! Supports javascript

Scraping senza supporto 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>

Scraping con supporto 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'

Puoi anche utilizzare dryscrape della libreria Python per raschiare siti Web guidati da JavaScript.

Scraping con supporto 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
Purtroppo, nessun supporto di Windows.
Expenzor,

1
Qualche alternativa per quelli di noi che programmano in Windows?
Hoshiko86,

2
@ExpenzorSto lavorando su Windows. PhantomJS funziona bene.
Aakash Choubey,

17
Vale la pena notare che PhantomJS è stato sospeso e non è più in fase di sviluppo attivo alla luce di Chrome che ora supporta senza testa. Si consiglia l'uso di chrome / firefox senza testa.
sytech,

3
È sia il supporto al selenio che lo stesso PhantomJS. github.com/ariya/phantomjs/issues/15344
sytech

74

Non stiamo ottenendo i risultati corretti perché qualsiasi contenuto generato da javascript deve essere reso sul DOM. Quando recuperiamo una pagina HTML, recuperiamo l'iniziale, non modificata da javascript, DOM.

Pertanto, è necessario eseguire il rendering del contenuto javascript prima di eseguire la scansione della pagina.

Poiché il selenio è già menzionato molte volte in questo thread (e anche la lentezza a volte viene menzionata), elencherò altre due possibili soluzioni.


Soluzione 1: questo è un tutorial molto carino su come usare Scrapy per eseguire la scansione dei contenuti generati da JavaScript e seguiremo proprio questo.

Di cosa avremo bisogno:

  1. Docker installato nella nostra macchina. Questo è un vantaggio rispetto ad altre soluzioni fino a questo punto, in quanto utilizza una piattaforma indipendente dal sistema operativo.

  2. Installa Splash seguendo le istruzioni elencate per il nostro sistema operativo corrispondente.
    Citando dalla documentazione iniziale:

    Splash è un servizio di rendering javascript. È un browser Web leggero con un'API HTTP, implementato in Python 3 usando Twisted e QT5.

    Essenzialmente useremo Splash per eseguire il rendering di contenuti generati da Javascript.

  3. Eseguire il server Splash: sudo docker run -p 8050:8050 scrapinghub/splash.

  4. Installa il plugin scrapy-splash :pip install scrapy-splash

  5. Supponendo che abbiamo già creato un progetto Scrapy (in caso contrario, facciamolo ), seguiremo la guida e aggiorneremo settings.py:

    Quindi vai al tuo progetto scrapy settings.pye imposta questi programmi:

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

    L'URL del server Splash (se si utilizza Win o OSX, questo dovrebbe essere l'URL della macchina docker: Come ottenere l'indirizzo IP del contenitore Docker dall'host? ):

    SPLASH_URL = 'http://localhost:8050'

    E infine è necessario impostare anche questi valori:

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
  6. Finalmente, possiamo usare a SplashRequest :

    In un ragno normale hai oggetti Richiesta che puoi usare per aprire gli URL. Se la pagina che si desidera aprire contiene dati generati da JS, è necessario utilizzare SplashRequest (o SplashFormRequest) per eseguire il rendering della pagina. Ecco un semplice esempio:

    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 esegue il rendering dell'URL come html e restituisce la risposta che è possibile utilizzare nel metodo callback (analisi).


Soluzione 2: chiamiamolo sperimentale al momento (maggio 2018) ...
Questa soluzione è solo per la versione 3.6 di Python (al momento).

Conosci il modulo richieste (bene chi non lo fa)?
Ora ha un piccolo fratello che striscia sul Web: request-HTML :

Questa libreria intende rendere l'analisi dell'HTML (ad es. Raschiando il Web) il più semplice e intuitivo possibile.

  1. Installa request-HTML: pipenv install requests-html

  2. Invia una richiesta all'URL della pagina:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
  3. Rendering della risposta per ottenere i bit generati da Javascript:

    r.html.render()

Infine, il modulo sembra offrire funzionalità di scraping .
In alternativa, possiamo provare il modo ben documentato di usare BeautifulSoup con l' r.htmloggetto che abbiamo appena reso.


puoi espandere su come ottenere il contenuto HTML completo, con bit JS caricati, dopo aver chiamato .render ()? Sono bloccato dopo quel punto. Non vedo tutti gli iframe che vengono iniettati nella pagina normalmente da JavaScript r.html.htmlnell'oggetto.
anon58192932,

@ anon58192932 Dato che al momento si tratta di una soluzione sperimentale e non so esattamente cosa stai cercando di ottenere di conseguenza, non posso davvero suggerire nulla ... Puoi creare una nuova domanda qui su SO se non l'hai fatto elaborò ancora una soluzione
John Moutafis,

2
Ho ricevuto questo errore: RuntimeError: Impossibile utilizzare HTMLSession in un loop di eventi esistente. Utilizzare invece AsyncHTMLSession.
HuckIt,

1
@HuckIt questo sembra essere un problema noto: github.com/psf/requests-html/issues/140
John Moutafis,

47

Forse il selenio può farlo.

from selenium import webdriver
import time

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

3
Il selenio è davvero pesante per questo genere di cose, che sarebbe inutilmente lento e richiederebbe una testa del browser se non si utilizza PhantomJS, ma funzionerebbe.
Joshua Hedges,

@JoshuaHedges Puoi eseguire altri browser più standard in modalità senza testa.
Reynoldsnlp

22

Se hai mai usato il Requestsmodulo per Python prima, ho scoperto di recente che lo sviluppatore ha creato un nuovo modulo chiamato Requests-HTMLche ora ha anche la possibilità di rendere JavaScript.

Puoi anche visitare https://html.python-requests.org/ per saperne di più su questo modulo, oppure se sei solo interessato a rendere JavaScript, puoi visitare https://html.python-requests.org/?#javascript -supporto per imparare direttamente come usare il modulo per rendere JavaScript usando Python.

In sostanza, una volta installato correttamente il Requests-HTMLmodulo, il seguente esempio, che viene mostrato sul link sopra , mostra come è possibile utilizzare questo modulo per raschiare un sito Web e renderizzare JavaScript contenuto nel sito Web:

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.

Di recente ho imparato questo da un video di YouTube. Clicca qui! per guardare il video di YouTube, che dimostra come funziona il modulo.


3
Si noti che questo modulo ha il supporto solo per Python 3.6.
nat5142,

1
Ho ricevuto questo errore: SSLError: HTTPSConnectionPool (host = 'docs.python-requests.org', porta = 443): Numero massimo di tentativi superato con url: / (Causato da SSLError (SSLError (1, '[SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert errore interno (_ssl.c: 1045) ')))
HuckIt

@HuckIt si scusa non ho familiarità con quell'errore, tuttavia l'errore sembra, il sito Web che si stava tentando di raggiungere potrebbe avere avuto un problema relativo alla certificazione SSL. Spiacenti, questa non è una soluzione, ma ti consiglierei di fare una nuova domanda, qui in overflow dello stack (se non è già stato chiesto) e possibilmente fornire maggiori dettagli come l'URL del sito web che stavi utilizzando e il tuo codice.
SShah,

Sembra che stia usando il cromo sotto il cappuccio. Per me funziona benissimo
Sid,

14

Anche questa sembra essere una buona soluzione, presa da un ottimo post sul blog

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

Sembra che i dati che stai davvero cercando siano accessibili tramite un URL secondario chiamato da alcuni javascript nella pagina principale.

Mentre potresti provare a eseguire javascript sul server per gestirlo, un approccio più semplice potrebbe essere quello di caricare la pagina utilizzando Firefox e utilizzare uno strumento come Charles o Firebug per identificare esattamente quale sia l'URL secondario. Quindi puoi semplicemente interrogare quell'URL direttamente per i dati che ti interessano.


@Kris Nel caso qualcuno dovesse inciampare su questo e vuole provarlo invece di qualcosa di pesante come il selenio, ecco un breve esempio. Questo aprirà la pagina dei dettagli della parte per un dado esagonale sul sito Web McMaster-Carr. Il contenuto del loro sito Web viene recuperato principalmente tramite Javascript e contiene pochissime informazioni sulla pagina nativa. Se apri gli strumenti di sviluppo del browser, vai alla scheda Rete e aggiorna la pagina, puoi vedere tutte le richieste fatte dalla pagina e trovare i dati rilevanti (in questo caso il dettaglio della parte html).
SweepingsDemon

Questo è un URL diverso che si trova nella scheda Rete devtool di Firefox che, se seguito, contiene l'html per la maggior parte delle informazioni sulla parte ed espone alcuni dei parametri richiesti per navigare facilmente verso altre informazioni sulla parte per facilitare lo scraping. Questo esempio particolare non è particolarmente utile poiché il prezzo è generato da un'altra funzione Javascript, ma dovrebbe servire abbastanza bene come introduzione a chiunque desideri seguire i consigli di Stephen.
SweepingsDemon

12

Il selenio è il migliore per raschiare il contenuto di JS e Ajax.

Consulta questo articolo per l' estrazione di dati dal Web utilizzando Python

$ pip install selenium

Quindi scarica il webdriver di Chrome.

from selenium import webdriver

browser = webdriver.Chrome()

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

nav = browser.find_element_by_id("mainnav")

print(nav.text)

Facile vero?


8

Puoi anche eseguire javascript usando webdriver.

from selenium import webdriver

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

o memorizzare il valore in una variabile

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

oppure puoi semplicemente usare la driver.titleproprietà
Corey Goldberg il

8

Personalmente preferisco usare scrapy e selenio e dockerizzare entrambi in contenitori separati. In questo modo è possibile installare sia con una seccatura minima sia eseguire la scansione di siti Web moderni che contengono quasi tutti javascript in un modo o nell'altro. Ecco un esempio:

Usa il scrapy startprojectper creare il tuo raschietto e scrivere il tuo ragno, lo scheletro può essere semplice come questo:

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)

La vera magia accade in middlewares.py. Sovrascrivi due metodi nel middleware downloader __init__e process_request, nel modo seguente:

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

Non dimenticare di abilitare questo middlware decommentando le righe successive nel file settings.py:

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

Avanti per la dockerizzazione. Crea il tuo Dockerfileda un'immagine leggera (sto usando python Alpine qui), copia la directory del progetto su di essa, installa i requisiti:

# 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

E infine riunisci tutto in 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

Corri docker-compose up -d. Se lo fai per la prima volta, ci vorrà un po 'prima che recuperi l'ultimo selenio / standalone-chrome e costruisca anche l'immagine del tuo raschietto.

Una volta fatto, puoi verificare che i tuoi contenitori siano in esecuzione docker pse anche che il nome del contenitore di selenio corrisponda a quello della variabile d'ambiente che abbiamo passato al nostro contenitore di raschietto (qui, eraSELENIUM_LOCATION=samplecrawler_selenium_1 ).

Inserisci il tuo contenitore raschietto con docker exec -ti YOUR_CONTAINER_NAME sh, il comando per me era docker exec -ti samplecrawler_my_scraper_1 sh, cd nella directory giusta ed esegui il raschietto con scrapy crawl my_spider.

L'intera cosa è sulla mia pagina github e puoi ottenerla da qui


5

Un mix di BeautifulSoup e Selenium funziona molto bene per me.

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 Puoi trovare altre condizioni di attesa qui


4

Ti consigliamo di utilizzare urllib, richieste, beautifulSoup e il driver web selenio nel tuo script per diverse parti della pagina, (solo per citarne alcuni).
A volte otterrai quello che ti serve solo con uno di questi moduli.
A volte avrai bisogno di due, tre o tutti questi moduli.
A volte dovrai disattivare js sul tuo browser.
A volte avrai bisogno di informazioni di intestazione nel tuo script.
Nessun sito Web può essere raschiato allo stesso modo e nessun sito Web può essere raschiato nello stesso modo per sempre senza dover modificare il crawler, di solito dopo alcuni mesi. Ma possono essere tutti raschiati! Dove c'è una volontà c'è sicuramente un modo.
Se hai bisogno di dati scansionati continuamente in futuro, basta raschiare tutto ciò di cui hai bisogno e archiviarli in file .dat con pickle.
Continua a cercare come provare cosa con questi moduli e a copiare e incollare i tuoi errori su Google.


3

Utilizzando 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

Ho cercato di trovare una risposta a queste domande per due giorni. Molte risposte ti indirizzano a diversi problemi. Ma la risposta di serpentr sopra è davvero al punto. È la soluzione più breve e semplice. Solo un promemoria l'ultima parola "var" rappresenta il nome della variabile , quindi dovrebbe essere usato come:

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

Questo dovrebbe essere un commento sulla risposta di serpentr, non una risposta separata.
Yserbius

1
Questo è ovvio. Ma non ho ancora 50 ripetizioni per commentare la risposta di qualcun altro.
Abd_bgc,

0

Ho dovuto affrontare questo stesso problema in alcuni progetti di web scraping. Il modo in cui l'ho risolto è stato utilizzando la libreria delle richieste di Python per effettuare una richiesta http direttamente all'API, anziché dover caricare JS.

La libreria di richieste di Python funziona bene per questo, e puoi vedere le richieste http usando inspect element e navigando alla scheda network.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.