web-dev-qa-db-fra.com

Web-scraping page JavaScript avec Python

J'essaie de développer un simple racleur Web. Je veux extraire du texte sans le code HTML. En fait, j’atteins cet objectif, mais j’ai vu que dans certaines pages où JavaScript est chargé, les résultats ne sont pas satisfaisants.

Par exemple, si du code JavaScript ajoute du texte, je ne le vois pas, car lorsque je l’appelle,

response = urllib2.urlopen(request)

Je reçois le texte original sans celui ajouté (car JavaScript est exécuté dans le client).

Donc, je cherche des idées pour résoudre ce problème.

149
mocopera

EDIT 30/Dec/2017: Cette réponse apparaît dans les meilleurs résultats des recherches Google. J'ai donc décidé de la mettre à jour. L'ancienne réponse est toujours à la fin.

dryscape n'est plus maintenu et la bibliothèque que les développeurs Dryscape recommandent est Python 2 uniquement. J'ai trouvé l'utilisation de la bibliothèque python de Selenium avec Phantom JS en tant que pilote Web suffisamment rapide et facile à effectuer.

Une fois que vous avez installé Phantom JS , assurez-vous que le binaire phantomjs est disponible dans le chemin actuel:

phantomjs --version
# result:
2.1.1

Exemple

Pour donner un exemple, j'ai créé un exemple de page avec le code HTML suivant. ( lien ):

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

sans javascript cela dit: No javascript support et avec javascript: Yay! Supports javascript

Raclage sans support 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>

Raclage avec support 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'

Vous pouvez également utiliser la bibliothèque Python dryscrape pour supprimer les sites Web gérés par javascript.

Raclage avec support 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>
190
avi

Nous n'obtenons pas les bons résultats car tout contenu généré par javascript doit être rendu sur le DOM. Lorsque nous récupérons une page HTML, nous récupérons l'initiale, non modifiée par javascript, DOM.

Par conséquent, nous devons restituer le contenu javascript avant d'explorer la page.

Comme Selenium est déjà mentionné à plusieurs reprises dans ce fil (et la lenteur avec laquelle il devient parfois mentionné également), je vais énumérer deux autres solutions possibles.


Solution 1: Ceci est un très bon tutoriel sur comment utiliser Scrapy pour analyser du contenu généré par javascript et nous allons suivre juste ça.

Ce dont nous aurons besoin:

  1. Docker installé sur notre machine. C'est un avantage sur les autres solutions jusqu'à présent, car il utilise une plate-forme indépendante du système d'exploitation.

  2. Installez Splash en suivant les instructions listées pour notre OS correspondant.
    Citations tirées de la documentation Splash:

    Splash est un service de rendu javascript. C’est un navigateur Web léger doté d’une API HTTP, implémenté dans Python 3 à l’aide de Twisted et de QT5.

    Nous allons essentiellement utiliser Splash pour rendre le contenu généré par Javascript.

  3. Exécutez le serveur de démarrage: Sudo docker run -p 8050:8050 scrapinghub/splash.

  4. Installez le plugin scrapy-splash : pip install scrapy-splash

  5. En supposant que nous ayons déjà un projet Scrapy créé (sinon, faisons-en un ), nous suivrons le guide et mettrons à jour le settings.py:

    Ensuite, accédez à settings.py de votre projet, et définissez les middlewares suivants:

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

    L’URL du serveur Splash (si vous utilisez Win ou OSX, c’est l’URL du docker: Comment obtenir l’adresse IP d’un conteneur Docker auprès de l’hôte? ):

    SPLASH_URL = 'http://localhost:8050'
    

    Enfin, vous devez également définir ces valeurs:

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    
  6. Enfin, nous pouvons utiliser un SplashRequest :

    Dans une araignée normale, vous avez des objets de requête que vous pouvez utiliser pour ouvrir des URL. Si la page que vous souhaitez ouvrir contient des données générées par JS, vous devez utiliser SplashRequest (ou SplashFormRequest) pour restituer la page. Voici un exemple simple:

    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 affiche l'URL au format HTML et renvoie la réponse que vous pouvez utiliser dans la méthode de rappel (analyse).


Solution 2: Appelons-le expérimental pour le moment (mai 2018) ...
Cette solution est destinée à la version 3.6 de Python uniquement (pour le moment).

Connaissez-vous le module demandes (bien qui ne le sait pas)?
Maintenant, il a un petit frère similaire à l'exploration Web: request-HTML :

Cette bibliothèque a pour but de rendre l’analyse HTML (par exemple, le nettoyage du Web) aussi simple et intuitive que possible.

  1. Installez request-html: pipenv install requests-html

  2. Faites une demande à l'URL de la page:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
    
  3. Rendez la réponse pour obtenir les bits générés par Javascript:

    r.html.render()
    

Enfin, le module semble offrir capacités de grattage .
Alternativement, nous pouvons essayer la méthode bien documentée d’utiliser BeautifulSoup avec l’objet r.html que nous venons de restituer.

48
John Moutafis

Peut-être que sélénium peut le faire.

from Selenium import webdriver
import time

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

Si vous avez déjà utilisé le module Requests pour python, j'ai récemment découvert que le développeur avait créé un nouveau module appelé Requests-HTML qui permet désormais de rendre le code JavaScript.

Vous pouvez également visiter https://html.python-requests.org/ pour en savoir plus sur ce module, ou si votre seul intérêt est le rendu de JavaScript, alors vous pouvez visiter https: // html.python-requests.org/?#javascript-support pour apprendre directement à utiliser le module pour rendre JavaScript en utilisant Python.

Pour résumer, une fois que vous avez correctement installé le module Requests-HTML, l'exemple suivant, affiché sur le lien ci-dessus , montre comment utiliser ce module pour gratter un site Web et afficher le code JavaScript contenu dans le fichier. site Internet:

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.

J'ai récemment appris à ce sujet d'une vidéo YouTube. Cliquez ici! pour regarder la vidéo sur YouTube, qui montre comment fonctionne le module.

16
SShah

Cela semble être une bonne solution également, tirée d'un excellent 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
14
marbel

Il semble que les données que vous recherchez réellement soient accessibles via une URL secondaire appelée par un code javascript sur la page principale.

Bien que vous puissiez essayer d’exécuter javascript sur le serveur pour gérer cela, une approche plus simple pourrait consister à charger la page à l’aide de Firefox et à utiliser un outil tel que Charles ou Firebug pour identifier exactement ce que cette URL secondaire est. Ensuite, vous pouvez simplement interroger directement cette URL pour connaître les données qui vous intéressent.

12
Stephen Emslie

Le sélénium est le meilleur moyen de supprimer le contenu JS et Ajax.

Consultez cet article pour extraire des données du Web à l'aide de Python

$ pip install Selenium

Ensuite, téléchargez Chrome webdriver.

from Selenium import webdriver

browser = webdriver.Chrome()

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

nav = browser.find_element_by_id("mainnav")

print(nav.text)

Facile, non?

9
Macnux

Vous pouvez également exécuter javascript à l'aide de webdriver.

from Selenium import webdriver

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

ou stocker la valeur dans une variable

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

Vous voudrez utiliser urllib, request, le pilote Web beautifulSoup et Selenium dans votre script pour différentes parties de la page (pour en nommer quelques-unes).
Vous obtiendrez parfois ce dont vous avez besoin avec un seul de ces modules.
Parfois, vous aurez besoin de deux, trois ou tous ces modules.
Parfois, vous devrez désactiver le js sur votre navigateur.
Vous aurez parfois besoin d’informations d’en-tête dans votre script.
Aucun site Web ne peut être gratté de la même manière et aucun site Web ne peut être gratté de la même manière pour toujours sans devoir modifier votre robot d'exploration, généralement après quelques mois. Mais ils peuvent tous être grattés! Là où il y a une volonté, il y a un moyen, bien sûr.
Si vous avez constamment besoin de données extraites, supprimez-les et stockez-les dans des fichiers .dat avec pickle.
Continuez simplement à chercher comment essayer quoi avec ces modules et copier-coller vos erreurs dans Google.

5
user7780801

Personnellement, je préfère utiliser scrapy et sélénium et dockerizing les deux dans des conteneurs séparés. De cette façon, vous pouvez installer à la fois avec un minimum de tracas et explorer des sites Web modernes contenant presque tous du javascript, sous une forme ou une autre. Voici un exemple:

Utilisez le scrapy startproject pour créer votre grattoir et écrire votre araignée. Le squelette peut être aussi simple que cela:

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 vraie magie se passe dans les middlewares.py. Remplacez deux méthodes du middleware de téléchargement, __init__ et process_request, de la manière suivante:

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

N'oubliez pas d'activer ce middleware en décommentant les lignes suivantes du fichier settings.py:

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

Suivant pour la dockerization. Créez votre Dockerfile à partir d'une image allégée (j'utilise python Alpine ici), copiez-y le répertoire de votre projet, installez la configuration requise suivante:

# 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

Et enfin, rassemblez tout cela dans 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

Exécutez docker-compose up -d. Si vous faites cela la première fois, il faudra un certain temps pour récupérer le dernier Selenium/standalone-chrome et pour construire votre image de grattoir.

Une fois que c'est fait, vous pouvez vérifier que vos conteneurs fonctionnent avec docker ps et également que le nom du conteneur Selenium correspond à celui de la variable d'environnement que nous avons transmise à notre conteneur de racleur (ici, il s'agissait de Selenium_LOCATION=samplecrawler_Selenium_1).

Entrez votre conteneur de racleur avec docker exec -ti YOUR_CONTAINER_NAME sh, la commande pour moi était docker exec -ti samplecrawler_my_scraper_1 sh, cd dans le bon répertoire et exécutez votre racleur avec scrapy crawl my_spider.

La chose entière est sur ma page github et vous pouvez l'obtenir de ici

4
tarikki

Un mélange de BeautifulSoup et de Selenium me convient très bien.

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. Vous pouvez trouver plus de conditions d'attente ici

4
Biarys

en utilisant 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
Ash-Ishh..