Python ile web kazıma JavaScript sayfası


178

Basit bir web kazıyıcı geliştirmeye çalışıyorum. HTML kodu olmadan metin ayıklamak istiyorum. Aslında, bu hedefe ulaşıyorum, ancak JavaScript'in yüklü olduğu bazı sayfalarda iyi sonuçlar elde etmediğimi gördüm.

Örneğin, bazı JavaScript kodları metin eklerse, onu göremiyorum, çünkü aradığımda

response = urllib2.urlopen(request)

Orijinal metni eklenmemiş olarak alıyorum (JavaScript istemcide yürütüldüğünden).

Bu sorunu çözmek için bazı fikirler arıyorum.


2
Daha ağır bir şeye ihtiyacınız olabilir gibi görünüyor, Selenyum veya Watir'i deneyin.
wim

2
Bunu başarıyla Java'da yaptım (Cobra araç setini kullandım lobobrowser.org/cobra.jsp ) Python'a (her zaman iyi bir seçim) saldırmak istediğinizden bu iki seçeneği öneriyorum: - packtpub.com/article/ python-part-2 ile web kazıma - blog.databigbang.com/web-scraping-ajax-and-javascript-sites
bpgergo

Yanıtlar:


203

30 EDIT / Dec / 2017: Bu cevap Google aramalarının en iyi sonuçlarında görünür, bu yüzden güncellemeye karar verdim. Eski cevap hala sonunda.

dryscape artık korunmuyor ve dryscape geliştiricilerinin tavsiye ettiği kütüphane sadece Python 2. Phantom JS ile Selenium'un python kütüphanesini kullanarak bir web sürücüsü olarak çalışmayı yapmak için yeterince hızlı ve kolay buldum.

Phantom JS'yi yükledikten sonra , phantomjsikili dosyanın geçerli yolda mevcut olduğundan emin olun :

phantomjs --version
# result:
2.1.1

Misal

Bir örnek vermek gerekirse, aşağıdaki HTML koduyla örnek bir sayfa oluşturdum. ( bağlantı ):

<!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 olmadan diyor ki: No javascript supportve javascript ile:Yay! Supports javascript

JS desteği olmadan kazıma:

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 desteği ile kazıma:

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'

Javascript tabanlı web sitelerini kazımak için Python kütüphanesi dryscrape'yi de kullanabilirsiniz .

JS desteği ile kazıma:

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
Ne yazık ki, Windows desteği yok.
Expenzor

1
Windows'da programlama yapanlarımız için herhangi bir alternatif var mı?
Hoshiko86

2
@ExpenzorPencerelerde çalışıyorum. PhantomJS iyi çalışıyor.
Aakash Choubey

17
PhantomJS'nin üretimi durduruldu ve artık Chrome'un ışığında artık başsızlığı destekleyen aktif geliştirme aşamasında değil. Başsız krom / firefox kullanılması önerilir.
sytech

3
Hem selenyum desteği hem de PhantomJS'nin kendisi. github.com/ariya/phantomjs/issues/15344
sytech

74

Javascript tarafından oluşturulan içeriklerin DOM'da oluşturulması gerektiğinden doğru sonuçları almıyoruz. Bir HTML sayfasını getirdiğimizde, javascript, DOM tarafından değiştirilmemiş ilk sayfayı getiririz.

Bu nedenle, sayfayı taramadan önce javascript içeriğini oluşturmamız gerekir.

Selenyum zaten bu iş parçacığında birçok kez belirtildiği gibi (ve bazen ne kadar yavaş geldiğinden de bahsedildi), diğer iki olası çözümü listeleyeceğim.


Çözüm 1: Bu, Javascript tarafından oluşturulan içeriği taramak için Scrapy'nin nasıl kullanılacağına dair çok güzel bir öğreticidir ve bunu takip edeceğiz.

Neye ihtiyacımız olacak:

  1. Docker makinemize kuruldu. Bu, OS'den bağımsız bir platform kullandığından, bu noktaya kadar diğer çözümlere göre bir artıdır.

  2. Splash'ı ilgili işletim sistemimiz için listelenen talimatları izleyerek kurun .
    Sıçrama belgelerinden alıntı:

    Splash bir javascript oluşturma servisidir. Twisted ve QT5 kullanarak Python 3'te uygulanan bir HTTP API'sine sahip hafif bir web tarayıcısıdır.

    Esasen, Javascript tarafından oluşturulan içeriği oluşturmak için Splash'ı kullanacağız.

  3. Sıçrama sunucuyu çalıştırın: sudo docker run -p 8050:8050 scrapinghub/splash.

  4. Scrapy-splash eklentisini yükleyin :pip install scrapy-splash

  5. Zaten bir Scrapy projesi oluşturduğumuzu varsayarsak (değilse, bir tane yapalım ), kılavuzu takip edip aşağıdakileri güncelleyeceğiz settings.py:

    Ardından, terapi projelerinize gidin settings.pyve şu ara ürünleri ayarlayın:

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

    Splash sunucusunun URL'si (Win veya OSX kullanıyorsanız, bu docker makinesinin URL'si olmalıdır: Ana bilgisayardan bir Docker kabının IP adresi nasıl alınır? ):

    SPLASH_URL = 'http://localhost:8050'

    Son olarak, bu değerleri de ayarlamanız gerekir:

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
  6. Son olarak şunları kullanabiliriz SplashRequest:

    Normal bir örümcekte, URL'leri açmak için kullanabileceğiniz İstek nesneleri vardır. Açmak istediğiniz sayfada JS tarafından oluşturulan veriler varsa, sayfayı oluşturmak için SplashRequest (veya SplashFormRequest) kullanmanız gerekir. İşte basit bir örnek:

    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'yi html olarak işler ve geri arama (ayrıştırma) yönteminde kullanabileceğiniz yanıtı döndürür.


Çözüm 2: Şu anda deneysel olarak adlandıralım (Mayıs 2018) ...
Bu çözüm yalnızca şu anda Python'un 3.6 sürümü içindir.

İstekler modülünü biliyor musunuz (kim bilmiyor)?
Şimdi küçük kardeş tarama bir web var: istekleri-HTML :

Bu kütüphane HTML'yi ayrıştırmayı (örn. Web'i kazıma) mümkün olduğunca basit ve sezgisel hale getirmeyi amaçlamaktadır.

  1. Yükleme istekleri-html: pipenv install requests-html

  2. Sayfanın URL'sine istekte bulunun:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
  3. Javascript tarafından oluşturulan bitleri almak için yanıtı oluşturun:

    r.html.render()

Son olarak, modül kazıma yetenekleri sunuyor gibi görünüyor .
Alternatif olarak, az önce oluşturduğumuz nesne ile BeautifulSoup'u kullanmanın iyi belgelenmiş yolunu deneyebiliriz r.html.


.render () çağrıldıktan sonra JS bitlerinin yüklü olduğu tam HTML içeriğinin nasıl alınacağını genişletebilir misiniz? O noktadan sonra sıkışıp kaldım. Sayfaya normalde JavaScript r.html.htmlnesneden enjekte iframe'leri görmüyorum .
anon58192932

@ anon58192932 Şu anda bu deneysel bir çözüm olduğundan ve sonuç olarak tam olarak neyi başarmaya çalıştığınızı bilmiyorum, gerçekten hiçbir şey öneremiyorum ... henüz bir çözüm geliştirdi
John Moutafis

2
Bu hatayı aldım: RuntimeError: Mevcut bir olay döngüsü içinde HTMLSession kullanılamıyor. Bunun yerine AsyncHTMLSession kullanın.
HuckIt

1
Bu bilinen bir sorun gibi görünüyor: github.com/psf/requests-html/issues/140
John

47

Belki selenyum yapabilir.

from selenium import webdriver
import time

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

3
Selenyum bu tür şeyler için gerçekten ağırdır, gereksiz yere yavaş olur ve PhantomJS kullanmazsanız bir tarayıcı kafası gerektirir, ancak bu işe yarar.
Joshua Hedges

@JoshuaHedges Diğer standart tarayıcıları başsız modda çalıştırabilirsiniz.
reynoldsnlp

22

Daha Requestsönce python için modülü kullandıysanız, geliştiricinin Requests-HTMLartık JavaScript oluşturma yeteneğine sahip yeni bir modül oluşturduğunu öğrendim .

Ayrıca , bu modül hakkında daha fazla bilgi edinmek için https://html.python-requests.org/ adresini ziyaret edebilir veya yalnızca JavaScript oluşturmakla ilgileniyorsanız https://html.python-requests.org/?#javascript adresini ziyaret edebilirsiniz. -Destek doğrudan Python kullanarak JavaScript oluşturmak için modülün nasıl kullanılacağını öğrenmek için.

Temel olarak, Requests-HTMLmodülü doğru bir şekilde yükledikten sonra , yukarıdaki bağlantıda gösterilen aşağıdaki örnek, bir web sitesini kazımak ve web sitesinde bulunan JavaScript'i oluşturmak için bu modülü nasıl kullanabileceğinizi gösterir:

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.

Yakın zamanda bunu bir YouTube videosundan öğrendim. Buraya Tıkla! modülün nasıl çalıştığını gösteren YouTube videosunu izlemek için.


3
Bu modülün yalnızca Python 3.6 desteği olduğunu unutmayın.
nat5142

1
Bu hatayı aldım: SSLError: HTTPSConnectionPool (host = 'docs.python-requests.org', port = 443): Maks. dahili hata (_ssl.c: 1045) ')))
HuckIt

@HuckIt appolojileri Bu hatayı bilmiyorum, ancak hata gibi görünüyor, ulaşmaya çalıştığınız web sitesinin SSL sertifikası ile ilgili bir sorunu olmuş olabilir. Üzgünüz, bu bir çözüm değil, ancak yeni bir soru yapmanızı tavsiye ederim, burada yığın taşması (henüz sorulmamışsa) ve muhtemelen kullandığınız web sitesi url'si ve kodunuz gibi daha fazla ayrıntı verin.
SShah

Kaputun altında krom kullanıyor gibi görünüyor. Gerçi benim için harika çalışıyor
Sid

14

Bu harika bir blog yayınından alınan iyi bir çözüm gibi görünüyor

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

Gerçekten aradığınız verilere, birincil sayfadaki bazı javascriptler tarafından çağrılan ikincil URL üzerinden erişilebileceği anlaşılıyor.

Bunu işlemek için sunucuda javascript çalıştırmayı deneyebilirsiniz, ancak daha basit bir yaklaşım, sayfayı Firefox kullanarak yüklemek ve ikincil URL'nin tam olarak ne olduğunu belirlemek için Charles veya Firebug gibi bir araç kullanmak olabilir . Ardından, ilgili URL'yi doğrudan ilgilendiğiniz veriler için sorgulayabilirsiniz.


@Kris Herkesin bunun üzerinde tökezlemesi ve selenyum kadar ağır bir şey yerine denemek istemesi durumunda, kısa bir örnek. Bu , McMaster-Carr web sitesinde altıgen bir somun için parça detay sayfasını açacaktır. Web sitesi içeriği çoğunlukla Javascript kullanılarak getirilir ve çok az yerel sayfa bilgisine sahiptir. Tarayıcı geliştirici araçlarınızı açar, Ağ sekmesine gidin ve sayfayı yenilerseniz, sayfa tarafından yapılan tüm istekleri görebilir ve ilgili verileri bulabilirsiniz (bu durumda parça detay html'si).
SweepingsDemon

Bu , Firefox devtool Ağı sekmesinde bulunan, takip edilirse, parça bilgilerinin çoğunda html içeren ve daha kolay kazıma için diğer parça bilgilerine kolayca gitmek için gereken bazı parametreleri ortaya çıkaran farklı bir url'dir. Bu özel örnek, fiyat başka bir Javascript işlevi tarafından üretildiğinden özellikle yararlı değildir, ancak Stephen'ın tavsiyelerini takip etmek isteyen herkese bir giriş olarak yeterince iyi hizmet vermelidir.
SweepingsDemon

12

Selenyum, JS ve Ajax içeriğini kazımak için en iyisidir.

Python kullanarak web'den veri ayıklamak için bu makaleye bakın

$ pip install selenium

Ardından Chrome web sürücüsünü indirin.

from selenium import webdriver

browser = webdriver.Chrome()

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

nav = browser.find_element_by_id("mainnav")

print(nav.text)

Kolay değil mi?


8

Javascript'i web sürücüsü kullanarak da yürütebilirsiniz.

from selenium import webdriver

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

veya değeri bir değişkende saklayabilir

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

ya da sadece driver.titleözelliği kullanabilirsiniz
Corey Goldberg

8

Şahsen scrapy ve selenyum kullanmayı ve her ikisini de ayrı kaplarda kullanmayı tercih ederim. Bu şekilde hem minimum güçlükle kurabilir, hem de hemen hemen hepsi bir biçimde javascript içeren modern web sitelerini tarayabilirsiniz. İşte bir örnek:

scrapy startprojectKazıyıcı oluşturmak ve örümcek yazmak için kullanın, iskelet bu kadar basit olabilir:

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)

Gerçek sihir midwares.py'de olur. İndirici katman içinde iki yöntem üzerine yaz, __init__ve process_requestşu şekilde:

# 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 dosyasındaki sonraki satırları kaldırarak bu aracıyı etkinleştirmeyi unutmayın:

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

Bağlantı istasyonu için bir sonraki. Senin oluşturma Dockerfilegereksinimlerini yüklemek, hafif görüntüden (kullanıyorum piton burada Alpin) buna proje dizinine kopyalayın:

# 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

Ve son olarak hepsini bir araya getirin 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

Koş docker-compose up -d. Bunu ilk kez yapıyorsanız, en son selenyum / bağımsız kromu getirmesi ve sıyırıcı görüntünüzü oluşturması biraz zaman alacaktır.

Tamamlandığında, kaplarınızın çalışıp çalışmadığını kontrol edebilir docker psve ayrıca selenyum kabının adının sıyırıcı kabımıza geçirdiğimiz ortam değişkeninin adıyla eşleşip eşleşmediğini kontrol edebilirsiniz (burada, öyleydi SELENIUM_LOCATION=samplecrawler_selenium_1).

Kazıyıcı kabınızı girin, docker exec -ti YOUR_CONTAINER_NAME shbenim için komut docker exec -ti samplecrawler_my_scraper_1 sh, doğru dizine cd ve kazıyıcıyı çalıştırın scrapy crawl my_spider.

Tüm şey benim github sayfasından olduğunu ve mal alabilirsiniz burada


5

BeautifulSoup ve Selenium'un bir karışımı benim için çok iyi çalışıyor.

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 Burada daha fazla bekleme koşulları bulabilirsiniz


4

Komut dosyanızda sayfanın farklı bölümleri için urllib, istek, beautifulSoup ve selenyum web sürücüsünü kullanmak istersiniz (birkaçını belirtmek için).
Bazen bu modüllerden sadece biriyle ihtiyacınız olanı elde edersiniz.
Bazen bu modüllerin ikisine, üçüne veya hepsine ihtiyacınız olabilir.
Bazen tarayıcınızdaki js'yi kapatmanız gerekebilir.
Bazen komut dosyanızda başlık bilgisine ihtiyacınız olabilir.
Hiçbir web sitesi aynı şekilde kazınamaz ve hiçbir web sitesi, genellikle birkaç ay sonra tarayıcınızı değiştirmek zorunda kalmadan sonsuza kadar aynı şekilde kazınamaz. Ama hepsi kazınabilir! Bir iradenin olduğu yerde kesin bir yol var.
Kazınmış verilere sürekli geleceğe ihtiyacınız varsa, ihtiyacınız olan her şeyi kazıyın ve turşu ile .dat dosyalarında saklayın.
Sadece bu modüllerle neyi denemek ve hatalarınızı Google'a kopyalayıp yapıştırmak için aramaya devam edin.


3

PyQt5 kullanma

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

Bu sorulara iki gündür cevap bulmaya çalışıyorum. Birçok cevap sizi farklı konulara yönlendirir. Ama serpentr'in yukarıdaki cevabı gerçekten önemli. En kısa, en basit çözümdür. Yalnızca "var" kelimesinin değişken adını temsil ettiğini hatırlatmak isteriz. Bu nedenle şu şekilde kullanılmalıdır:

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

Bu, ayrı bir cevap değil, serpentr'in cevabı hakkında bir yorum olmalıdır.
Yserbius

1
Bu çok açık. Ama başka birinin cevabı hakkında henüz yorum yapmak için henüz 50 temsilcim yok.
Abd_bgc

0

Kendi web kazıma projelerinde de aynı problemle uğraşmak zorunda kaldım. Ben onunla nasıl başa JS yüklemek zorunda yerine doğrudan API bir http isteği yapmak için python istekleri kitaplığı kullanarak oldu.

Python istek kitaplığı bunun için iyi çalışır ve inspect öğesini kullanarak ve ağ sekmesine giderek http isteklerini görebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.