Dapatkah goresan digunakan untuk mengikis konten dinamis dari situs web yang menggunakan AJAX?


145

Saya baru-baru ini belajar Python dan sedang mencelupkan tangan saya ke dalam membangun web-scraper. Tidak ada yang mewah sama sekali; satu-satunya tujuan adalah untuk mendapatkan data dari situs web taruhan dan meminta data ini dimasukkan ke dalam Excel.

Sebagian besar masalah dapat dipecahkan dan saya mengalami sedikit kekacauan. Namun saya memukul rintangan besar atas satu masalah. Jika sebuah situs memuat tabel kuda dan mencantumkan harga taruhan saat ini, informasi ini tidak ada dalam file sumber apa pun. Petunjuknya adalah bahwa data ini kadang-kadang hidup, dengan angka yang diperbarui jelas dari beberapa server jarak jauh. HTML di PC saya hanya memiliki lubang di mana server mereka mendorong melalui semua data menarik yang saya butuhkan.

Sekarang pengalaman saya dengan konten web dinamis rendah, jadi hal ini adalah sesuatu yang saya mengalami kesulitan dalam menggerakkan kepala.

Saya pikir Java atau Javascript adalah kunci, ini sering muncul.

Scraper hanyalah sebuah mesin pembanding peluang. Beberapa situs memiliki API tetapi saya membutuhkan ini untuk mereka yang tidak. Saya menggunakan perpustakaan kotor dengan Python 2.7

Saya minta maaf jika pertanyaan ini terlalu terbuka. Singkatnya, pertanyaan saya adalah: bagaimana goresan dapat digunakan untuk mengikis data dinamis ini sehingga saya dapat menggunakannya? Sehingga saya dapat mengikis data peluang taruhan ini secara waktu nyata?


1
Bagaimana saya bisa mendapatkan data ini, data yang dinamis dan hidup?
Joseph

1
Jika halaman Anda memiliki javascript, Coba ini
reclosedev

3
Coba beberapa Firefoxekstensi seperti httpFoxatau liveHttpHeadersdan muat halaman yang menggunakan permintaan ajax. Scrapy tidak secara otomatis mengidentifikasi permintaan ajax, Anda harus mencari secara manual URL ajax yang sesuai dan kemudian melakukan permintaan dengan itu.
Aamir Adnan

tepuk tangan, saya akan memberikan ekstensi Firefox wizz
Joseph

Ada sejumlah solusi open source. Tetapi jika Anda mencari cara yang mudah dan cepat untuk melakukan ini terutama untuk beban kerja yang besar, periksa SnapSearch ( snapsearch.io ). Itu dibangun untuk situs JS, HTML5 dan SPA yang membutuhkan kemampuan penelusuran mesin pencari. Coba demo (jika ada konten kosong, ini berarti situs tersebut sebenarnya tidak mengembalikan konten tubuh, yang berpotensi berarti pengalihan 301).
CMCDragonkai

Jawaban:


74

Browser berbasis webkit (seperti Google Chrome atau Safari) memiliki alat pengembang bawaan. Di Chrome Anda dapat membukanya Menu->Tools->Developer Tools. The Networktab memungkinkan Anda untuk melihat semua informasi tentang setiap permintaan dan respon:

masukkan deskripsi gambar di sini

Di bagian bawah gambar Anda dapat melihat bahwa saya telah memfilter permintaan ke XHR- ini adalah permintaan yang dibuat oleh kode javascript.

Tip: log dihapus setiap kali Anda memuat halaman, di bagian bawah gambar, tombol titik hitam akan menyimpan log.

Setelah menganalisis permintaan dan tanggapan, Anda dapat mensimulasikan permintaan ini dari perayap web dan mengekstrak data berharga. Dalam banyak kasus akan lebih mudah untuk mendapatkan data Anda daripada parsing HTML, karena data itu tidak mengandung logika presentasi dan diformat untuk diakses oleh kode javascript.

Firefox memiliki ekstensi yang serupa, disebut firebug . Beberapa orang akan berpendapat bahwa firebug bahkan lebih kuat tetapi saya suka kesederhanaan webkit.


141
Bagaimana ini bisa menjadi jawaban yang diterima jika bahkan tidak ada kata 'kotor' di dalamnya ??
Toolkit

Ini berfungsi, dan mudah untuk menguraikan menggunakan modul json dengan python. Itu solusinya! Dibandingkan dengan itu, coba gunakan selenium atau hal-hal lain yang orang sarankan, itu lebih sakit kepala. Jika metode alternatif jauh lebih berbelit-belit, maka saya akan memberikannya kepada Anda, tetapi tidak demikian halnya di sini @Toolkit
Arion_Miles

1
Ini tidak terlalu relevan. Pertanyaannya adalah bagaimana menggunakan scarpy untuk mengikis situs web yang dinamis.
E. Erfan

"Bagaimana ini bisa menjadi jawaban yang diterima" - Karena penggunaan praktis mengalahkan ketepatan politik. Manusia mengerti KONTEKS.
Espresso

98

Berikut adalah contoh sederhana scrapydengan permintaan AJAX. Coba lihat situs rubin-kazan.ru .

Semua pesan dimuat dengan permintaan AJAX. Tujuan saya adalah mengambil pesan ini dengan semua atributnya (penulis, tanggal, ...):

masukkan deskripsi gambar di sini

Ketika saya menganalisis kode sumber halaman saya tidak dapat melihat semua pesan ini karena halaman web menggunakan teknologi AJAX. Tapi saya bisa dengan Firebug dari Mozilla Firefox (atau alat yang setara di browser lain) untuk menganalisis permintaan HTTP yang menghasilkan pesan di halaman web:

masukkan deskripsi gambar di sini

Itu tidak memuat ulang seluruh halaman tetapi hanya bagian-bagian dari halaman yang berisi pesan. Untuk tujuan ini saya klik nomor halaman yang sewenang-wenang di bagian bawah:

masukkan deskripsi gambar di sini

Dan saya mengamati permintaan HTTP yang bertanggung jawab untuk isi pesan:

masukkan deskripsi gambar di sini

Setelah selesai, saya menganalisis tajuk permintaan (saya harus mengutip bahwa URL ini akan saya ekstrak dari halaman sumber dari bagian var, lihat kode di bawah ini):

masukkan deskripsi gambar di sini

Dan isi data formulir permintaan (metode HTTP adalah "Posting"):

masukkan deskripsi gambar di sini

Dan konten respon, yang merupakan file JSON:

masukkan deskripsi gambar di sini

Yang menyajikan semua informasi yang saya cari.

Mulai sekarang, saya harus menerapkan semua pengetahuan ini dengan kasar. Mari kita tentukan laba-laba untuk tujuan ini:

class spider(BaseSpider):
    name = 'RubiGuesst'
    start_urls = ['http://www.rubin-kazan.ru/guestbook.html']

    def parse(self, response):
        url_list_gb_messages = re.search(r'url_list_gb_messages="(.*)"', response.body).group(1)
        yield FormRequest('http://www.rubin-kazan.ru' + url_list_gb_messages, callback=self.RubiGuessItem,
                          formdata={'page': str(page + 1), 'uid': ''})

    def RubiGuessItem(self, response):
        json_file = response.body

Dalam parsefungsi saya mendapat respons untuk permintaan pertama. Dalam RubiGuessItemSaya memiliki file JSON dengan semua informasi.


6
Hai. Bisakah Anda jelaskan apa itu 'url_list_gb_messages'? Saya tidak bisa memahaminya. Terima kasih.
polarisasi

4
Yang ini pasti lebih baik.
1a1a11a

1
@polarise Kode itu menggunakan remodul (ekspresi reguler), ia mencari string 'url_list_gb_messages="(.*)"'dan mengisolasi konten tanda kurung dalam variabel nama yang sama. Ini adalah pengantar yang bagus: guru99.com/python- regular
MGP

42

Sering kali ketika perayapan kami mengalami masalah di mana konten yang dirender pada halaman dihasilkan dengan Javascript dan karenanya tidak dapat menjelajah untuk itu (mis. Permintaan ajax, kegilaan jQuery).

Namun, jika Anda menggunakan Scrapy bersama dengan kerangka pengujian web Selenium maka kami dapat merayapi apa pun yang ditampilkan di browser web normal.

Beberapa hal yang perlu diperhatikan:

  • Anda harus menginstal Selthon RC versi Python agar bisa berfungsi, dan Anda harus mengatur Selenium dengan benar. Juga ini hanya perayap templat. Anda bisa menjadi lebih gila dan lebih maju dengan hal-hal tetapi saya hanya ingin menunjukkan ide dasar. Sebagai kode berdiri sekarang Anda akan melakukan dua permintaan untuk setiap url yang diberikan. Satu permintaan dibuat oleh Scrapy dan yang lainnya dibuat oleh Selenium. Saya yakin ada beberapa cara untuk mengatasi hal ini sehingga Anda mungkin bisa membuat Selenium melakukan satu-satunya permintaan, tetapi saya tidak repot-repot mengimplementasikannya dan dengan melakukan dua permintaan, Anda bisa menjelajah halaman dengan Scrapy juga.

  • Ini cukup kuat karena sekarang Anda memiliki seluruh DOM yang disediakan untuk Anda jelajahi dan Anda masih dapat menggunakan semua fitur perayapan yang bagus di Scrapy. Ini akan membuat perayapan lebih lambat tentu saja, tetapi tergantung pada seberapa banyak Anda membutuhkan DOM yang diberikan, mungkin layak untuk ditunggu.

    from scrapy.contrib.spiders import CrawlSpider, Rule
    from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
    from scrapy.selector import HtmlXPathSelector
    from scrapy.http import Request
    
    from selenium import selenium
    
    class SeleniumSpider(CrawlSpider):
        name = "SeleniumSpider"
        start_urls = ["http://www.domain.com"]
    
        rules = (
            Rule(SgmlLinkExtractor(allow=('\.html', )), callback='parse_page',follow=True),
        )
    
        def __init__(self):
            CrawlSpider.__init__(self)
            self.verificationErrors = []
            self.selenium = selenium("localhost", 4444, "*chrome", "http://www.domain.com")
            self.selenium.start()
    
        def __del__(self):
            self.selenium.stop()
            print self.verificationErrors
            CrawlSpider.__del__(self)
    
        def parse_page(self, response):
            item = Item()
    
            hxs = HtmlXPathSelector(response)
            #Do some XPath selection with Scrapy
            hxs.select('//div').extract()
    
            sel = self.selenium
            sel.open(response.url)
    
            #Wait for javscript to load in Selenium
            time.sleep(2.5)
    
            #Do some crawling of javascript created content with Selenium
            sel.get_text("//div")
            yield item
    
    # Snippet imported from snippets.scrapy.org (which no longer works)
    # author: wynbennett
    # date  : Jun 21, 2011

Referensi: http://snipplr.com/view/66998/


Solusi rapi! Apakah Anda punya tips untuk menghubungkan skrip ini ke Firefox? (OS adalah Linux Mint). Saya mendapatkan "[Errno 111] Koneksi ditolak".
Andrew

1
Kode ini tidak lagi berfungsi untuk selenium=3.3.1dan python=2.7.10, galat ketika mengimpor selenium dari selenium
benjaminz

1
Dalam versi selenium pernyataan impor Anda akan menjadi: from selenium import webdriveratau chromedriveratau apa pun yang Anda kebetulan menggunakan. Docs EDIT: Tambahkan referensi dokumentasi dan ubah tata bahasa saya yang mengerikan!
nulltron

Selenium Remote Control telah digantikan oleh Selenium WebDriver, menurut situs web mereka
rainbowsorbet

33

Solusi lain adalah dengan mengimplementasikan download handler atau mengunduh handler middleware. (lihat dokumen kasar untuk informasi lebih lanjut tentang middleware pengunduh) Berikut ini adalah contoh kelas menggunakan selenium dengan webdriver phantomjs tanpa kepala:

1) Tentukan kelas dalam middlewares.pyskrip.

from selenium import webdriver
from scrapy.http import HtmlResponse

class JsDownload(object):

    @check_spider_middleware
    def process_request(self, request, spider):
        driver = webdriver.PhantomJS(executable_path='D:\phantomjs.exe')
        driver.get(request.url)
        return HtmlResponse(request.url, encoding='utf-8', body=driver.page_source.encode('utf-8'))

2) Tambahkan JsDownload()kelas ke variabel DOWNLOADER_MIDDLEWAREdalam settings.py:

DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}

3) Mengintegrasikan ke HTMLResponsedalam your_spider.py. Mendekode badan respons akan memberikan Anda hasil yang diinginkan.

class Spider(CrawlSpider):
    # define unique name of spider
    name = "spider"

    start_urls = ["https://www.url.de"] 

    def parse(self, response):
        # initialize items
        item = CrawlerItem()

        # store data as items
        item["js_enabled"] = response.body.decode("utf-8") 

Addon Opsional:
Saya ingin kemampuan untuk memberitahu laba-laba berbeda yang middleware gunakan sehingga saya mengimplementasikan pembungkus ini:

def check_spider_middleware(method):
@functools.wraps(method)
def wrapper(self, request, spider):
    msg = '%%s %s middleware step' % (self.__class__.__name__,)
    if self.__class__ in spider.middleware:
        spider.log(msg % 'executing', level=log.DEBUG)
        return method(self, request, spider)
    else:
        spider.log(msg % 'skipping', level=log.DEBUG)
        return None

return wrapper

agar pembungkus berfungsi, semua laba-laba harus memiliki minimum:

middleware = set([])

untuk memasukkan middleware:

middleware = set([MyProj.middleware.ModuleName.ClassName])

Keuntungan: Keuntungan
utama untuk menerapkannya dengan cara ini daripada dalam laba-laba adalah bahwa Anda hanya membuat satu permintaan. Dalam solusi AT, misalnya: Handler unduhan memproses permintaan dan kemudian memberikan respons terhadap laba-laba. Laba-laba kemudian membuat permintaan baru di fungsi parse_page - Dua permintaan untuk konten yang sama.


Saya agak terlambat untuk menjawab ini>. <
rocktheartsm4l

@ rocktheartsm4l apa yang salah dengan hanya menggunakan, dalam process_requests, if spider.name in ['spider1', 'spider2']bukan dekorator
pad

@ Pad Tidak ada yang salah dengan itu. Saya baru saja menemukan lebih jelas bagi kelas laba-laba saya untuk memiliki satu set bernama middleware. Dengan cara ini saya bisa melihat kelas laba-laba mana saja dan melihat middlewares mana yang akan dieksekusi untuknya. Proyek saya sudah menerapkan banyak middleware jadi ini masuk akal.
rocktheartsm4l

Ini solusi yang mengerikan. Tidak hanya itu tidak terkait dengan kasar tetapi kode itu sendiri sangat tidak efisien serta seluruh pendekatan secara umum mengalahkan seluruh tujuan kerangka kerja pengeruk web asinkron yang kasar
Granitosaurus

2
Ini jauh lebih efisien daripada solusi lain yang pernah saya lihat di SO karena menggunakan perangkat pengunduh menengah membuatnya jadi hanya satu permintaan yang dibuat untuk halaman .. jika begitu mengerikan mengapa Anda tidak datang dengan solusi yang lebih baik dan berbagi bukan membuat klaim satu sisi secara terang-terangan. "Tidak ada hubungannya dengan goresan" apakah kamu merokok sesuatu? Selain menerapkan beberapa solusi gila yang kompleks, kuat dan kustom, ini adalah pendekatan yang saya lihat kebanyakan orang gunakan. Satu-satunya perbedaan adalah bahwa sebagian besar menerapkan bagian selenium dalam laba-laba yang menyebabkan beberapa permintaan dibuat ...
rocktheartsm4l

10

Saya menggunakan middleware pengunduh khusus, tetapi tidak terlalu senang dengannya, karena saya tidak berhasil membuat cache berfungsi dengannya.

Pendekatan yang lebih baik adalah menerapkan handler unduhan khusus.

Ada contoh kerja di sini . Ini terlihat seperti ini:

# encoding: utf-8
from __future__ import unicode_literals

from scrapy import signals
from scrapy.signalmanager import SignalManager
from scrapy.responsetypes import responsetypes
from scrapy.xlib.pydispatch import dispatcher
from selenium import webdriver
from six.moves import queue
from twisted.internet import defer, threads
from twisted.python.failure import Failure


class PhantomJSDownloadHandler(object):

    def __init__(self, settings):
        self.options = settings.get('PHANTOMJS_OPTIONS', {})

        max_run = settings.get('PHANTOMJS_MAXRUN', 10)
        self.sem = defer.DeferredSemaphore(max_run)
        self.queue = queue.LifoQueue(max_run)

        SignalManager(dispatcher.Any).connect(self._close, signal=signals.spider_closed)

    def download_request(self, request, spider):
        """use semaphore to guard a phantomjs pool"""
        return self.sem.run(self._wait_request, request, spider)

    def _wait_request(self, request, spider):
        try:
            driver = self.queue.get_nowait()
        except queue.Empty:
            driver = webdriver.PhantomJS(**self.options)

        driver.get(request.url)
        # ghostdriver won't response when switch window until page is loaded
        dfd = threads.deferToThread(lambda: driver.switch_to.window(driver.current_window_handle))
        dfd.addCallback(self._response, driver, spider)
        return dfd

    def _response(self, _, driver, spider):
        body = driver.execute_script("return document.documentElement.innerHTML")
        if body.startswith("<head></head>"):  # cannot access response header in Selenium
            body = driver.execute_script("return document.documentElement.textContent")
        url = driver.current_url
        respcls = responsetypes.from_args(url=url, body=body[:100].encode('utf8'))
        resp = respcls(url=url, body=body, encoding="utf-8")

        response_failed = getattr(spider, "response_failed", None)
        if response_failed and callable(response_failed) and response_failed(resp, driver):
            driver.close()
            return defer.fail(Failure())
        else:
            self.queue.put(driver)
            return defer.succeed(resp)

    def _close(self):
        while not self.queue.empty():
            driver = self.queue.get_nowait()
            driver.close()

Misalkan scraper Anda disebut "scraper". Jika Anda memasukkan kode yang disebutkan di dalam file yang disebut handlers.py di root folder "scraper", maka Anda dapat menambahkan ke settings.py Anda:

DOWNLOAD_HANDLERS = {
    'http': 'scraper.handlers.PhantomJSDownloadHandler',
    'https': 'scraper.handlers.PhantomJSDownloadHandler',
}

Dan voila, JS parsing DOM, dengan cache kotor, coba lagi, dll.


Saya suka solusi ini!
rocktheartsm4l

Solusi bagus Apakah driver Selenium masih merupakan satu-satunya pilihan?
Motheus

Solusi bagus Terima kasih banyak.
CrazyGeek

4

bagaimana goresan dapat digunakan untuk mengikis data dinamis ini sehingga saya dapat menggunakannya?

Saya heran mengapa tidak ada yang memposting solusi menggunakan Scrapy saja.

Lihat pos blog dari tim Scrapy SCRAPING INFINITE PAGRING SCROLLING . Contohnya memo situs web http://spidyquotes.herokuapp.com/scroll yang menggunakan pengguliran tak terbatas.

Idenya adalah untuk menggunakan Alat Pengembang browser Anda dan perhatikan permintaan AJAX, kemudian berdasarkan informasi itu buat permintaan untuk Scrapy .

import json
import scrapy


class SpidyQuotesSpider(scrapy.Spider):
    name = 'spidyquotes'
    quotes_base_url = 'http://spidyquotes.herokuapp.com/api/quotes?page=%s'
    start_urls = [quotes_base_url % 1]
    download_delay = 1.5

    def parse(self, response):
        data = json.loads(response.body)
        for item in data.get('quotes', []):
            yield {
                'text': item.get('text'),
                'author': item.get('author', {}).get('name'),
                'tags': item.get('tags'),
            }
        if data['has_next']:
            next_page = data['page'] + 1
            yield scrapy.Request(self.quotes_base_url % next_page)

Kami menghadapi masalah yang sama lagi: Scrappy tidak dibuat untuk tujuan ini dan di sinilah kami dihadapkan pada masalah yang sama. Pindah ke phantomJS atau seperti yang disarankan orang lain, buat middleware unduhan Anda sendiri
rak007

@ rak007 PhantomJS vs driver Chrome. Mana yang akan Anda sarankan?
Chankey Pathak

2

ya, Scrapy dapat memo situs web dinamis, situs web yang dirender melalui javaScript.

Ada dua pendekatan untuk menggagalkan situs web semacam ini.

Pertama,

Anda dapat menggunakan splashuntuk membuat kode Javascript dan kemudian menguraikan HTML yang diberikan. Anda dapat menemukan dokumen dan proyek di sini Scash splash, git

Kedua,

Karena semua orang menyatakan, dengan memonitor network calls, ya, Anda dapat menemukan panggilan api yang mengambil data dan mengejek panggilan itu di laba-laba Anda yang kasar dapat membantu Anda mendapatkan data yang diinginkan.


1

Saya menangani permintaan ajax dengan menggunakan Selenium dan driver web Firefox. Ini tidak secepat jika Anda memerlukan crawler sebagai daemon, tetapi jauh lebih baik daripada solusi manual. Saya menulis tutorial singkat di sini untuk referensi

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.