Halaman JavaScript pengikis web dengan Python

178

Saya mencoba mengembangkan pengikis web sederhana. Saya ingin mengekstraksi teks tanpa kode HTML. Sebenarnya, saya mencapai tujuan ini, tetapi saya telah melihat bahwa di beberapa halaman di mana JavaScript dimuat saya tidak mendapatkan hasil yang baik.

Misalnya, jika beberapa kode JavaScript menambahkan beberapa teks, saya tidak dapat melihatnya, karena ketika saya menelepon

response = urllib2.urlopen(request)

Saya mendapatkan teks asli tanpa yang ditambahkan (karena JavaScript dijalankan di klien).

Jadi, saya mencari beberapa ide untuk menyelesaikan masalah ini.

mocopera
sumber
2
Kedengarannya Anda mungkin membutuhkan sesuatu yang lebih berat, coba Selenium atau Watir.
wim
2
Saya telah berhasil melakukan ini di Jawa (Saya telah menggunakan Cobra toolkit lobobrowser.org/cobra.jsp ) Karena Anda ingin meretas dengan python (selalu merupakan pilihan yang baik), saya merekomendasikan dua opsi ini: - packtpub.com/article/ web-scraping-with-python-part-2 - blog.databigbang.com/web-scraping-ajax-and-javascript-sites
bpgergo

Jawaban:

203

EDIT 30 / Des / 2017: Jawaban ini muncul di hasil pencarian Google teratas, jadi saya memutuskan untuk memperbaruinya. Jawaban lama masih di akhir.

dryscape tidak dipelihara lagi dan perpustakaan dryscape merekomendasikan pengembang adalah Python 2 saja. Saya telah menemukan menggunakan perpustakaan python Selenium dengan Phantom JS sebagai driver web cukup cepat dan mudah untuk menyelesaikan pekerjaan.

Setelah Anda menginstal Phantom JS , pastikan phantomjsbiner tersedia di jalur saat ini:

phantomjs --version
# result:
2.1.1

Contoh

Sebagai contoh, saya membuat halaman contoh dengan kode HTML berikut. ( tautan ):

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

tanpa javascript dikatakan: No javascript supportdan dengan javascript:Yay! Supports javascript

Menggores tanpa dukungan 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>

Menggores dengan dukungan 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'

Anda juga dapat menggunakan dryscrape pustaka Python untuk mengikis situs web yang digerakkan oleh javascript.

Menggores dengan dukungan 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>
avi
sumber
16
Sayangnya, tidak ada dukungan Windows.
Expenzor
1
Adakah alternatif bagi kita yang memprogram dalam Windows?
Hoshiko86
2
@ExpenzorSaya sedang mengerjakan windows. PhantomJS berfungsi dengan baik.
Aakash Choubey
17
Patut dicatat bahwa PhantomJS telah dihentikan dan tidak lagi dalam pengembangan aktif karena Chrome sekarang mendukung tanpa kepala. Disarankan menggunakan chrome tanpa kepala / firefox.
sytech
3
Keduanya merupakan dukungan selenium dan PhantomJS sendiri. github.com/ariya/phantomjs/issues/15344
sytech
74

Kami tidak mendapatkan hasil yang benar karena konten yang dihasilkan javascript perlu di-render pada DOM. Ketika kami mengambil halaman HTML, kami mengambil yang awal, tidak dimodifikasi oleh javascript, DOM.

Karena itu kita perlu merender konten javascript sebelum kita merayapi halaman.

Karena selenium telah disebutkan berulang kali di utas ini (dan seberapa lambat terkadang juga disebutkan), saya akan mencantumkan dua solusi lain yang mungkin.


Solusi 1: Ini adalah tutorial yang sangat bagus tentang cara menggunakan Scrapy untuk merayapi konten yang dihasilkan javascript dan kami akan mengikuti hal itu.

Apa yang akan kita butuhkan:

  1. Docker dipasang di mesin kami. Ini merupakan nilai tambah dari solusi lain sampai saat ini, karena menggunakan platform OS-independen.

  2. Instal Splash mengikuti instruksi yang tercantum untuk OS kami yang sesuai.
    Mengutip dari dokumentasi splash:

    Splash adalah layanan rendering javascript. Ini adalah browser web yang ringan dengan API HTTP, diimplementasikan dalam Python 3 menggunakan Twisted dan QT5.

    Pada dasarnya kita akan menggunakan Splash untuk membuat konten Javascript yang dihasilkan.

  3. Jalankan server percikan: sudo docker run -p 8050:8050 scrapinghub/splash.

  4. Instal plugin goresan-percikan :pip install scrapy-splash

  5. Dengan asumsi bahwa kami sudah memiliki proyek Scrapy dibuat (jika tidak, mari kita buat satu ), kami akan mengikuti panduan ini dan memperbarui settings.py:

    Lalu pergi ke proyek Anda yang berantakan settings.pydan atur middlewares ini:

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

    URL server Splash (jika Anda menggunakan Win atau OSX ini harus menjadi URL mesin docker: Bagaimana cara mendapatkan alamat IP kontainer Docker dari host? ):

    SPLASH_URL = 'http://localhost:8050'

    Dan akhirnya Anda perlu mengatur nilai-nilai ini juga:

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
  6. Akhirnya, kita dapat menggunakan SplashRequest:

    Dalam laba-laba normal, Anda memiliki objek Permintaan yang dapat Anda gunakan untuk membuka URL. Jika halaman yang ingin Anda buka berisi data yang dihasilkan JS, Anda harus menggunakan SplashRequest (atau SplashFormRequest) untuk merender halaman. Berikut ini contoh sederhana:

    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 merender URL sebagai html dan mengembalikan respons yang dapat Anda gunakan dalam metode callback (parse).


Solusi 2: Mari kita sebut percobaan ini saat ini (Mei 2018) ...
Solusi ini hanya untuk versi Python 3,6 (saat ini).

Apakah Anda tahu modul permintaan (baik siapa yang tidak)?
Sekarang memiliki web merangkak adik kecil: permintaan-HTML :

Pustaka ini bermaksud membuat parsing HTML (misalnya mengikis web) sesederhana dan seintuitif mungkin.

  1. Instal permintaan-html: pipenv install requests-html

  2. Buat permintaan ke url halaman:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
  3. Berikan respons untuk mendapatkan bit yang dihasilkan Javascript:

    r.html.render()

Akhirnya, modul ini tampaknya menawarkan kemampuan mengikis .
Sebagai alternatif, kita dapat mencoba cara terdokumentasi yang baik dalam menggunakan BeautifulSoup dengan r.htmlobjek yang baru saja kita buat.

John Moutafis
sumber
dapatkah Anda mengembangkan cara mendapatkan konten HTML lengkap, dengan bit JS dimuat, setelah memanggil .render ()? Saya terjebak setelah titik itu. Saya tidak melihat semua iframe yang disuntikkan ke halaman biasanya dari JavaScript di r.html.htmlobjek.
anon58192932
@ anon58192932 Karena saat ini ini adalah solusi eksperimental dan saya tidak tahu persis apa yang ingin Anda capai sebagai hasilnya, saya tidak bisa menyarankan apa pun ... Anda dapat membuat pertanyaan baru di sini di SO jika Anda belum belum menemukan solusi
John Moutafis
2
Saya mendapat kesalahan ini: RuntimeError: Tidak dapat menggunakan HTMLSession dalam loop acara yang ada. Gunakan AsyncHTMLSession sebagai gantinya.
HuckIt
1
@HuckIni ini tampaknya menjadi masalah yang diketahui: github.com/psf/requests-html/issues/140
John Moutafis
47

Mungkin selenium bisa melakukannya.

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source
menakjubkan di sana
sumber
3
Selenium sangat berat untuk hal semacam ini, yang tidak perlu lambat dan memerlukan kepala browser jika Anda tidak menggunakan PhantomJS, tetapi ini akan berhasil.
Joshua Hedges
@ JoshuaHedges Anda dapat menjalankan browser standar lainnya dalam mode tanpa kepala.
reynoldsnlp
22

Jika Anda pernah menggunakan Requestsmodul untuk python sebelumnya, saya baru-baru ini menemukan bahwa pengembang membuat modul baru yang disebut Requests-HTMLsekarang juga memiliki kemampuan untuk membuat JavaScript.

Anda juga dapat mengunjungi https://html.python-requests.org/ untuk mempelajari lebih lanjut tentang modul ini, atau jika Anda hanya tertarik tentang rendering JavaScript maka Anda dapat mengunjungi https://html.python-requests.org/?#javascript -dukungan untuk secara langsung belajar bagaimana menggunakan modul untuk membuat JavaScript menggunakan Python.

Pada dasarnya, Setelah Anda menginstal Requests-HTMLmodul dengan benar , contoh berikut, yang ditunjukkan pada tautan di atas , menunjukkan bagaimana Anda dapat menggunakan modul ini untuk mengikis situs web dan membuat JavaScript yang terkandung dalam situs 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.

Baru-baru ini saya mengetahui hal ini dari video YouTube. Klik disini! untuk menonton video YouTube, yang menunjukkan cara kerja modul.

Sshah
sumber
3
Harus dicatat bahwa modul ini hanya memiliki dukungan untuk Python 3.6.
nat5142
1
Saya mendapat kesalahan ini: SSLError: HTTPSConnectionPool (host = 'docs.python-requests.org', port = 443): Max coba lagi melebihi dengan url: / (Disebabkan oleh SSLError (SSLError (1, '[SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert kesalahan internal (_ssl.c: 1045) ')))
HuckIt
@HuckIt appologies Saya tidak terbiasa dengan kesalahan itu, namun kesalahannya sepertinya, situs web yang Anda coba jangkau mungkin memiliki masalah terkait sertifikasi SSL. Maaf ini bukan solusi, tetapi saya akan merekomendasikan Anda untuk membuat pertanyaan baru, di sini di stack overflow (jika belum ditanyakan) dan mungkin memberikan rincian lebih lanjut seperti url situs web yang Anda gunakan dan kode Anda.
SShah
Tampaknya menggunakan kromium di bawah tenda. Bekerja dengan baik untuk saya
Sid
14

Ini tampaknya menjadi solusi yang baik juga, diambil dari posting blog yang bagus

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
marbel
sumber
12

Kedengarannya seperti data yang benar-benar Anda cari dapat diakses melalui URL sekunder yang disebut oleh beberapa javascript di halaman utama.

Meskipun Anda dapat mencoba menjalankan javascript di server untuk menangani hal ini, pendekatan yang lebih sederhana adalah memuat halaman menggunakan Firefox dan menggunakan alat seperti Charles atau Firebug untuk mengidentifikasi dengan tepat apa itu URL sekunder. Kemudian Anda bisa langsung menanyakan URL itu untuk data yang Anda minati.

Stephen Emslie
sumber
@Kris Kalau-kalau ada yang tersandung pada ini dan ingin mencobanya alih-alih sesuatu yang seberat selenium, inilah contoh singkatnya. Ini akan membuka halaman detail bagian untuk hex nut di situs web McMaster-Carr. Konten situs web mereka sebagian besar diambil menggunakan Javascript dan memiliki informasi halaman asli yang sangat sedikit. Jika Anda membuka alat pengembang peramban, menavigasi ke tab Network, dan menyegarkan halaman, Anda dapat melihat semua permintaan yang dibuat oleh halaman dan menemukan data yang relevan (dalam hal ini html detail bagian).
SweepingsDemon
Ini adalah url berbeda yang ditemukan di tab Firefox devtool Network yang, jika diikuti, berisi html untuk sebagian besar informasi bagian dan memaparkan beberapa parameter yang diperlukan untuk menavigasi dengan mudah ke informasi bagian lain agar mudah dikikis. Contoh khusus ini tidak terlalu berguna karena harga dihasilkan oleh fungsi Javascript lain, tetapi harus berfungsi cukup baik sebagai pengantar kepada siapa pun yang ingin mengikuti saran Stephen.
SweepingsDemon
12

Selenium adalah yang terbaik untuk mengikis konten JS dan Ajax.

Periksa artikel ini untuk mengekstraksi data dari web menggunakan Python

$ pip install selenium

Kemudian unduh driver web Chrome.

from selenium import webdriver

browser = webdriver.Chrome()

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

nav = browser.find_element_by_id("mainnav")

print(nav.text)

Mudah kan?

Macnux
sumber
8

Anda juga dapat menjalankan javascript menggunakan webdriver.

from selenium import webdriver

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

atau menyimpan nilai dalam suatu variabel

result = driver.execute_script('var text = document.title ; return var')
Serpentr
sumber
atau Anda bisa menggunakan driver.titleproperti
Corey Goldberg
8

Saya pribadi lebih suka menggunakan kasar dan selenium dan merendam keduanya dalam wadah terpisah. Dengan cara ini Anda dapat menginstal keduanya dengan kerumitan minimal dan merayapi situs web modern yang hampir semuanya berisi javascript dalam satu bentuk atau lainnya. Ini sebuah contoh:

Gunakan scrapy startprojectuntuk membuat scraper Anda dan tuliskan laba-laba Anda, kerangkanya bisa sesederhana ini:

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)

Keajaiban yang sebenarnya terjadi di middlewares.py. Timpa dua metode dalam middleware pengunduh, __init__dan process_request, dengan cara berikut:

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

Jangan lupa untuk mengaktifkan middlware ini dengan menghapus komentar pada baris berikutnya dalam file settings.py:

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

Selanjutnya untuk buruh pelabuhan. Buat Anda Dockerfiledari gambar yang ringan (Saya menggunakan python Alpine di sini), salin direktori proyek Anda untuk itu, instal persyaratan:

# 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

Dan akhirnya kumpulkan semuanya dalam 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

Lari docker-compose up -d. Jika Anda melakukan ini pertama kali akan butuh waktu cukup lama untuk mengambil selenium / standalone-chrome terbaru dan membuat gambar scraper Anda juga.

Setelah selesai, Anda dapat memeriksa apakah wadah Anda berjalan docker psdan juga memeriksa bahwa nama wadah selenium cocok dengan variabel lingkungan yang kami berikan ke wadah scraper kami (di sini, itu SELENIUM_LOCATION=samplecrawler_selenium_1).

Masukkan wadah scraper Anda dengan docker exec -ti YOUR_CONTAINER_NAME sh, perintah untuk saya adalah docker exec -ti samplecrawler_my_scraper_1 sh, cd ke direktori yang benar dan jalankan scraper Anda dengan scrapy crawl my_spider.

Semuanya ada di halaman github saya dan Anda bisa mendapatkannya dari sini

tarikki
sumber
5

Perpaduan antara BeautifulSoup dan Selenium sangat baik bagi saya.

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 Anda dapat menemukan lebih banyak kondisi tunggu di sini

Biary
sumber
4

Anda akan ingin menggunakan urllib, permintaan, beautifulSoup, dan driver web selenium di skrip Anda untuk bagian halaman yang berbeda, (untuk beberapa nama).
Terkadang Anda akan mendapatkan apa yang Anda butuhkan hanya dengan salah satu modul ini.
Terkadang Anda membutuhkan dua, tiga, atau semua modul ini.
Terkadang Anda harus mematikan js di browser Anda.
Terkadang Anda membutuhkan info tajuk di skrip Anda.
Tidak ada situs web yang dapat dikikis dengan cara yang sama dan tidak ada situs web yang dapat dikikis dengan cara yang sama selamanya tanpa harus memodifikasi perayap Anda, biasanya setelah beberapa bulan. Tapi mereka semua bisa dikikis! Di mana ada kemauan pasti ada jalan.
Jika Anda membutuhkan data yang tergores secara terus-menerus ke masa depan, cukup gesek semua yang Anda butuhkan dan simpan dalam file dat dengan acar.
Teruslah mencari cara mencoba apa dengan modul ini dan menyalin dan menempel kesalahan Anda ke Google.


sumber
3

Menggunakan 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)
Ash-Ishh ..
sumber
1

Saya sudah mencoba menemukan jawaban untuk pertanyaan ini selama dua hari. Banyak jawaban mengarahkan Anda ke masalah yang berbeda. Tetapi jawaban serpentr di atas benar-benar to the point. Ini adalah solusi tersingkat dan paling sederhana. Hanya pengingat kata terakhir "var" mewakili nama variabel , jadi harus digunakan sebagai:

 result = driver.execute_script('var text = document.title ; return text')
Abd_bgc
sumber
Ini harus menjadi komentar pada jawaban serpentr, bukan jawaban yang terpisah.
Yserbius
1
Itu sudah jelas. Tetapi saya belum memiliki 50 repetisi untuk mengomentari jawaban orang lain.
Abd_bgc
0

Saya harus berurusan dengan masalah yang sama pada beberapa proyek pengikisan web saya sendiri. Bagaimana saya mengatasinya adalah dengan menggunakan pustaka permintaan python untuk membuat permintaan http langsung ke API, daripada harus memuat JS.

Pustaka permintaan python berfungsi dengan baik untuk ini, dan Anda dapat melihat permintaan http dengan menggunakan elemen inspeksi dan menavigasi ke tab jaringan.

Superduperfluous
sumber