Page Web-scraping JavaScript avec Python

j'essaie de développer un grattoir web simple. Je veux extraire du texte sans le code HTML. En fait, j'ai atteint cet objectif, mais j'ai vu que dans certaines pages où JavaScript est chargé, Je n'ai pas obtenu de bons résultats.

par exemple, si un code JavaScript ajoute du texte, Je ne peux pas le voir, parce que quand j'appelle

response = urllib2.urlopen(request)

j'obtiens le texte original sans ajout (parce que JavaScript est exécuté dans le client).

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

120
demandé sur Paul Rooney 2011-11-08 15:13:51

12 réponses

EDIT 30 / Dec / 2017: cette réponse apparaît dans les premiers résultats des recherches Google, donc j'ai 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 de dryscape recommandent est Python 2 seulement. J'ai trouvé que L'utilisation de la bibliothèque Python de sélénium avec Phantom JS comme pilote web était assez rapide et facile pour que le travail soit fait.

une fois que vous avez installé Phantom js , assurez-vous le binaire phantomjs est disponible dans le chemin courant:

phantomjs --version
# result:
2.1.1

exemple

pour donner un exemple, j'ai créé une page échantillon 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 il est écrit: No javascript support et avec javascript: Yay! Supports javascript

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

grattage 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 gratter les sites Web JavaScript.

grattage 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>
160
répondu avi 2017-12-30 13:18:07

Peut-être 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
36
répondu amazingthere 2016-04-14 09:31:25

nous n'obtenons pas les bons résultats parce que 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.

nous avons donc besoin de rendre le contenu javascript avant de parcourir la page.

étant donné que le sélénium est déjà mentionné plusieurs fois dans ce fil (et que la lenteur est parfois mentionnée également), je vais énumérer deux autres solutions possibles.


Solution 1: il s'agit d'un très beau tutoriel sur comment utiliser Scrapy pour Crawler le contenu généré par javascript et nous allons le suivre.

ce dont nous aurons besoin:

  1. Docker installé dans notre machine. C'est un plus par rapport à d'autres solutions jusqu'à ce point, comme il utilise une plate-forme indépendante du système D'exploitation.

  2. installez Splash en suivant les instructions énumérées pour notre système D'exploitation correspondant.

    citation de la documentation splash:

    Splash est un service de rendu javascript. C'est un navigateur Web léger avec une API HTTP, implémenté en Python 3 en utilisant Twisted et QT5.

    Essentiellement, nous allons utiliser Splash pour rendre le contenu généré par Javascript.

  3. lancer le serveur splash: sudo docker run -p 8050:8050 scrapinghub/splash .

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

  5. en supposant que nous avons déjà un projet Scrapy créé (sinon, faisons-en un ), nous allons suivre le guide et mettre à jour le settings.py :

    puis allez à votre projet de scrapy settings.py et mettez ces middlewares:

    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, ceci devrait être L'URL de la machine docker: Comment obtenir l'adresse IP d'un conteneur Docker de l'hôte? ):

    SPLASH_URL = 'http://localhost:8050'
    

    et finalement vous devez définir ces valeurs aussi:

    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 Request que vous pouvez utiliser pour ouvrir des URLs. Si la page que vous voulez ouvrir contient des données générées par JS, vous devez utiliser SplashRequest(ou SplashFormRequest) pour rendre 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 rend l'URL html et renvoie la réponse que vous pouvez utiliser dans la méthode callback(parse).


Solution 2: appelons cette expérimentation pour le moment (mai 2018)...

Cette solution est pour la version 3.6 de Python seulement (à l'heure actuelle).

Connaissez-vous le module requests (Eh bien, comment ne pas le savoir)?

Maintenant il a un web rampant petit frère: requêtes-HTML :

cette bibliothèque a l'intention de rendre L'analyse HTML (par ex. gratter le web) aussi simple et intuitive que possible.

  1. Installer demandes-html: pipenv install requests-html

  2. faire une demande à l'url de la page:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
    
  3. Rendre la réponse pour obtenir le code Javascript généré bits:

    r.html.render()
    

enfin, le module semble offrir raclage capacités .

Alternativement, nous pouvons essayer la manière bien documentée d'utiliser le BeautifulSoup avec l'objet r.html que nous venons de rendre.

24
répondu John Moutafis 2018-05-30 19:52:45

Cela semble être une bonne solution aussi, prises à partir d'un blog post

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
13
répondu marbel 2017-03-13 08:45:14

il semble que les données que vous recherchez vraiment peuvent être consultées via une URL secondaire appelée par un certain javascript sur la page principale.

alors que vous pourriez essayer d'exécuter javascript sur le serveur pour gérer cela, une approche plus simple pourrait être de charger la page en utilisant Firefox et utiliser un outil comme Charles ou Firebug pour identifier exactement ce que cette URL secondaire est. Alors vous pouvez juste interroger cette URL directement pour le les données qui vous intéressent.

12
répondu Stephen Emslie 2011-11-08 11:23:50

si vous avez déjà utilisé le module Requests pour python auparavant, j'ai récemment découvert que le développeur a créé un nouveau module appelé Requests-HTML qui a maintenant la capacité de rendre JavaScript.

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

essentiellement, une fois que vous installez correctement le module Requests-HTML , l'exemple suivant , qui est montré sur le lien ci-dessus , montre comment vous pouvez utiliser ce module pour gratter un site web et rendre JavaScript contenu dans le site:

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 sur YouTube. Cliquez Ici! pour regarder la vidéo YouTube, qui montre comment le module fonctionne.

9
répondu SShah 2018-04-24 18:11:47

le sélénium est le meilleur pour le raclage de la teneur en JS et Ajax.

voir cet article https://likegeeks.com/python-web-scraping /

$ pip install selenium

téléchargez ensuite 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?

6
répondu Macnux 2018-04-18 22:11:53

vous pouvez également exécuter javascript en utilisant 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')
5
répondu Serpentr 2017-03-28 16:45:24

vous voudrez utiliser urllib, requests, beautifulSoup et pilote de web de sélénium dans votre script pour différentes parties de la page, (pour n'en nommer que quelques-unes).

Parfois, vous obtiendrez ce dont vous avez besoin avec un seul de ces modules.

Parfois, vous aurez besoin de deux, trois, ou tous ces modules.

Parfois, vous aurez besoin d'éteindre le js sur votre navigateur.

Parfois, vous aurez besoin d'informations d'en-tête dans votre script.

Aucun site web ne peut être raclé de la même manière et aucun site web ne peut être raclé de la même manière pour toujours sans avoir à modifier votre crawler, généralement après quelques mois. Mais ils peuvent tous être raclée! Où il ya une volonté il ya un chemin sûr.

Si vous avez besoin de données raclées en permanence dans le futur, il suffit de racler tout ce dont vous avez besoin et de le stocker .dat fichiers avec cornichon.

Continuez à chercher comment essayer quoi avec ces modules et copier et coller vos erreurs dans Google.

4
répondu 2017-03-28 17:06:08

un mélange de sucette et de sélénium fonctionne très bien pour moi.

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

3
répondu Biarys 2018-05-29 22:29:20

personnellement, je préfère utiliser scrapy et sélénium et Docker les deux dans des conteneurs séparés. De cette façon, vous pouvez installer à la fois avec un minimum de tracas et crawl sites Web modernes qui contiennent presque tous 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 ceci:

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 produit dans le middlewares.py. Réécriture deux méthodes dans le 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 cet middlware en décommentant les lignes suivantes dans le settings.py dossier:

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

suivant pour dockerization. Créez votre Dockerfile à partir d'une image légère (j'utilise python Alpine ici), copiez votre répertoire de projet, installez les exigences:

# 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 rassembler 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écuter docker-compose up -d . Si vous faites cela la première fois, il vous faudra du temps pour obtenir la dernière version de selenium/standalone-chrome et pour construire votre image scraper.

une fois que c'est fait, vous pouvez vérifier que vos conteneurs fonctionnent avec docker ps et aussi vérifier que le nom du conteneur de sélénium correspond à celui de la variable d'environnement que nous avons passé à notre grattoir conteneur (ici, c'était SELENIUM_LOCATION=samplecrawler_selenium_1 ).

entrez votre 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 répertoire de droite et lancez votre racleur avec scrapy crawl my_spider .

toute la chose est sur ma page github et vous pouvez l'obtenir de ici

2
répondu tarikki 2018-05-30 19:21:37

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
répondu Ash-Ishh.. 2018-07-14 16:44:29