Có thể sử dụng phế liệu để cạo nội dung động từ các trang web đang sử dụng AJAX không?


145

Gần đây tôi đã học Python và đang nhúng tay vào việc xây dựng một trình quét web. Không có gì lạ mắt cả; Mục đích duy nhất của nó là lấy dữ liệu của một trang web cá cược và đưa dữ liệu này vào Excel.

Hầu hết các vấn đề đều có thể giải quyết được và tôi đang có một mớ hỗn độn nhỏ. Tuy nhiên, tôi đang gặp một trở ngại lớn về một vấn đề. Nếu một trang web tải một bảng ngựa và liệt kê giá cá cược hiện tại, thông tin này không có trong bất kỳ tệp nguồn nào. Manh mối là dữ liệu này đôi khi tồn tại, với những con số được cập nhật rõ ràng từ một số máy chủ từ xa. HTML trên PC của tôi chỉ đơn giản là có một lỗ hổng nơi máy chủ của họ đang lướt qua tất cả dữ liệu thú vị mà tôi cần.

Bây giờ trải nghiệm của tôi với nội dung web động còn thấp, vì vậy đây là điều tôi gặp khó khăn trong đầu.

Tôi nghĩ Java hoặc Javascript là một chìa khóa, cái này bật lên thường xuyên.

Các cạp chỉ đơn giản là một công cụ so sánh tỷ lệ cược. Một số trang web có API nhưng tôi cần điều này cho những trang không có. Tôi đang sử dụng thư viện phế liệu với Python 2.7

Tôi xin lỗi nếu câu hỏi này quá mở. Tóm lại, câu hỏi của tôi là: làm thế nào có thể sử dụng phế liệu để cạo dữ liệu động này để tôi có thể sử dụng nó? Để tôi có thể cạo dữ liệu tỷ lệ cá cược này trong thời gian thực?


1
Làm cách nào tôi có thể nhận được dữ liệu này, dữ liệu động và sống?
Giuse

1
Nếu trang của bạn có javascript, hãy thử điều này
reclosesev

3
Hãy thử một số Firefoxtiện ích mở rộng như httpFoxhoặc liveHttpHeaderstải trang đang sử dụng yêu cầu ajax. Scrapy không tự động xác định các yêu cầu ajax, bạn phải tìm kiếm thủ công URL ajax thích hợp và sau đó thực hiện yêu cầu đó.
Aamir Adnan

chúc mừng, tôi sẽ cung cấp cho các tiện ích mở rộng của Firefox một wizz
Joseph

Có một số giải pháp nguồn mở. Nhưng nếu bạn đang tìm kiếm một cách dễ dàng và nhanh chóng để thực hiện việc này đặc biệt là cho khối lượng công việc lớn, hãy xem SnapSearch ( snapsearch.io ). Nó được xây dựng cho các trang web JS, HTML5 và SPA yêu cầu khả năng thu thập dữ liệu của công cụ tìm kiếm. Hãy thử bản demo (nếu có nội dung trống, điều này có nghĩa là trang web thực sự không trả lại nội dung cơ thể, có khả năng có nghĩa là chuyển hướng 301).
CMCDragonkai

Câu trả lời:


74

Các trình duyệt dựa trên Webkit (như Google Chrome hoặc Safari) có các công cụ dành cho nhà phát triển tích hợp. Trong Chrome, bạn có thể mở nó Menu->Tools->Developer Tools. Các Networktab cho phép bạn xem tất cả các thông tin về tất cả các yêu cầu và phản ứng:

nhập mô tả hình ảnh ở đây

Ở dưới cùng của hình ảnh, bạn có thể thấy rằng tôi đã lọc yêu cầu xuống XHR- đây là những yêu cầu được thực hiện bằng mã javascript.

Mẹo: nhật ký sẽ bị xóa mỗi khi bạn tải một trang, ở dưới cùng của hình ảnh, nút chấm đen sẽ giữ nhật ký.

Sau khi phân tích các yêu cầu và phản hồi, bạn có thể mô phỏng các yêu cầu này từ trình thu thập dữ liệu web của mình và trích xuất dữ liệu có giá trị. Trong nhiều trường hợp, việc lấy dữ liệu của bạn sẽ dễ dàng hơn so với phân tích HTML, vì dữ liệu đó không chứa logic trình bày và được định dạng để được truy cập bằng mã javascript.

Firefox có phần mở rộng tương tự, nó được gọi là fireorms . Một số người sẽ cho rằng firebird thậm chí còn mạnh hơn nhưng tôi thích sự đơn giản của webkit.


141
Làm thế quái nào đây có thể là một câu trả lời được chấp nhận nếu nó thậm chí không có từ 'phế liệu' trong đó ??
Bộ công cụ

Nó hoạt động và thật dễ dàng để phân tích bằng mô-đun json trong python. Đó là một giải pháp! So với điều đó, hãy thử sử dụng selen hoặc những thứ khác mà mọi người đang đề xuất, nó đau đầu hơn. Nếu phương pháp thay thế trở nên phức tạp hơn thì tôi sẽ đưa nó cho bạn, nhưng đó không phải là trường hợp ở đây @Toolkit
Arion_Miles

1
Điều này không thực sự có liên quan. Câu hỏi là làm thế nào để sử dụng Scarpy để cạo các trang web động.
E. Erfan

"Làm thế quái nào đây có thể là một câu trả lời được chấp nhận" - Bởi vì sử dụng thực tế đánh bại tính chính xác. Con người hiểu TIẾP THEO.
Espresso

98

Đây là một ví dụ đơn giản scrapyvới yêu cầu AJAX. Hãy xem trang web rubin-kazan.ru .

Tất cả các tin nhắn được tải với một yêu cầu AJAX. Mục tiêu của tôi là tìm nạp các tin nhắn này với tất cả các thuộc tính của chúng (tác giả, ngày, ...):

nhập mô tả hình ảnh ở đây

Khi tôi phân tích mã nguồn của trang, tôi không thể thấy tất cả các thông báo này vì trang web sử dụng công nghệ AJAX. Nhưng tôi có thể với Fireorms từ Mozilla Firefox (hoặc một công cụ tương đương trong các trình duyệt khác) để phân tích yêu cầu HTTP tạo thông báo trên trang web:

nhập mô tả hình ảnh ở đây

Nó không tải lại toàn bộ trang mà chỉ các phần của trang có chứa tin nhắn. Với mục đích này, tôi nhấp vào số trang tùy ý ở phía dưới:

nhập mô tả hình ảnh ở đây

Và tôi quan sát yêu cầu HTTP chịu trách nhiệm về nội dung thư:

nhập mô tả hình ảnh ở đây

Sau khi kết thúc, tôi phân tích các tiêu đề của yêu cầu (Tôi phải trích dẫn rằng URL này tôi sẽ trích xuất từ ​​trang nguồn từ phần var, xem mã bên dưới):

nhập mô tả hình ảnh ở đây

Và nội dung dữ liệu biểu mẫu của yêu cầu (phương thức HTTP là "Đăng"):

nhập mô tả hình ảnh ở đây

Và nội dung của phản hồi, đó là một tệp JSON:

nhập mô tả hình ảnh ở đây

Mà trình bày tất cả các thông tin tôi đang tìm kiếm.

Từ bây giờ, tôi phải thực hiện tất cả các kiến ​​thức này trong phế liệu. Hãy xác định con nhện cho mục đích này:

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

Trong parsechức năng tôi có phản hồi cho yêu cầu đầu tiên. Trong RubiGuessItemtôi có tệp JSON với tất cả thông tin.


6
Chào. Bạn có thể giải thích 'url_list_gb_messages' là gì không? Tôi không thể hiểu nó. Cảm ơn.
phân cực

4
Điều này chắc chắn là tốt hơn.
1a1a11a

1
@polarise Mã đó đang sử dụng remô-đun (biểu thức chính quy), nó tìm kiếm chuỗi 'url_list_gb_messages="(.*)"'và cô lập nội dung của dấu ngoặc trong biến có cùng tên. Đây là một đoạn giới thiệu tuyệt vời: guru99.com/python-THER-expressions-complete-tutorial.html
MGP

42

Nhiều khi thu thập thông tin, chúng tôi gặp phải các vấn đề trong đó nội dung được hiển thị trên trang được tạo bằng Javascript và do đó, không thể thu thập dữ liệu cho nó (ví dụ: yêu cầu ajax, điên cuồng jQuery).

Tuy nhiên, nếu bạn sử dụng Scrapy cùng với khung thử nghiệm web Selenium thì chúng tôi có thể thu thập dữ liệu mọi thứ được hiển thị trong trình duyệt web bình thường.

Một số điều cần lưu ý:

  • Bạn phải cài đặt phiên bản Python của Selenium RC để nó hoạt động và bạn phải thiết lập Selenium đúng cách. Ngoài ra đây chỉ là một trình thu thập mẫu. Bạn có thể trở nên điên rồ hơn và tiến bộ hơn với mọi thứ nhưng tôi chỉ muốn thể hiện ý tưởng cơ bản. Vì mã hiện tại, bạn sẽ thực hiện hai yêu cầu cho bất kỳ url nào. Một yêu cầu được đưa ra bởi Scrapy và yêu cầu khác được thực hiện bởi Selenium. Tôi chắc chắn có nhiều cách để giải quyết vấn đề này để bạn có thể yêu cầu Selenium thực hiện một yêu cầu duy nhất nhưng tôi không bận tâm thực hiện điều đó và bằng cách thực hiện hai yêu cầu bạn có thể thu thập dữ liệu trang bằng Scrapy.

  • Điều này khá mạnh mẽ vì bây giờ bạn có toàn bộ DOM được hiển thị để bạn thu thập thông tin và bạn vẫn có thể sử dụng tất cả các tính năng thu thập dữ liệu đẹp trong Scrapy. Điều này sẽ làm cho việc thu thập dữ liệu chậm hơn tất nhiên nhưng tùy thuộc vào mức độ bạn cần DOM được kết xuất, nó có thể đáng để chờ đợi.

    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

Tham khảo: http://snipplr.com/view/66998/


Giải pháp gọn gàng! Bạn có bất cứ lời khuyên nào về việc kết nối tập lệnh này với Firefox không? (Hệ điều hành là Linux Mint). Tôi đang nhận được "[Errno 111] Kết nối bị từ chối".
Andrew

Mã này không còn hoạt động cho selenium=3.3.1python=2.7.10, lỗi khi nhập selen từ selenium
benjaminz

Trong phiên bản selen đó, câu lệnh nhập của bạn sẽ là: from selenium import webdriver hoặc chromedriverhoặc bất cứ điều gì bạn tình cờ được sử dụng. Tài liệu EDIT: Thêm tài liệu tham khảo và thay đổi ngữ pháp khủng khiếp của tôi!
nulltron

Điều khiển từ xa Selenium đã được thay thế bởi Selenium WebDriver, theo trang web của họ
rainbowsorbet

33

Một giải pháp khác là triển khai trình xử lý tải xuống hoặc tải xuống phần mềm trung gian xử lý. (xem tài liệu sơ sài để biết thêm thông tin về phần mềm trung gian của trình tải xuống) Sau đây là một lớp ví dụ sử dụng selenium với webler phlessjs không đầu:

1) Xác định lớp trong middlewares.pytập lệnh.

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) Thêm JsDownload()lớp vào biến DOWNLOADER_MIDDLEWAREtrong settings.py:

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

3) Tích hợp HTMLResponsebên trong your_spider.py. Giải mã cơ thể phản hồi sẽ giúp bạn có đầu ra mong muốn.

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 tùy chọn:
Tôi muốn có khả năng cho các con nhện khác nhau sử dụng phần mềm trung gian nào nên tôi đã triển khai trình bao bọc này:

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

để trình bao bọc hoạt động, tất cả các con nhện phải có tối thiểu:

middleware = set([])

để bao gồm một phần mềm trung gian:

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

Ưu điểm: Ưu điểm
chính để thực hiện theo cách này chứ không phải trong con nhện là bạn chỉ thực hiện một yêu cầu. Ví dụ, trong giải pháp của AT: Trình xử lý tải xuống xử lý yêu cầu và sau đó đưa ra phản hồi cho con nhện. Con nhện sau đó đưa ra một yêu cầu hoàn toàn mới trong chức năng parse_page của nó - Đó là hai yêu cầu cho cùng một nội dung.


Tôi đã khá muộn để trả lời điều này mặc dù>. <
rocktheartsm4l

@ rocktheartsm4l gì sai với chỉ sử dụng, trong process_requests, if spider.name in ['spider1', 'spider2']thay vì trang trí
pad

@pad Không có gì sai với điều đó. Tôi chỉ thấy rõ hơn cho các lớp nhện của tôi có một bộ có tên là phần mềm trung gian. Bằng cách này tôi có thể nhìn vào bất kỳ lớp nhện nào và xem chính xác phần mềm trung gian nào sẽ được thực thi cho nó. Dự án của tôi đã có rất nhiều phần mềm trung gian được triển khai nên điều này có ý nghĩa.
rocktheartsm4l

Đây là một giải pháp khủng khiếp. Không chỉ nó không liên quan đến phế liệu mà bản thân mã này cực kỳ kém hiệu quả cũng như toàn bộ cách tiếp cận nói chung đánh bại toàn bộ mục đích của khung quét web không đồng bộ là phế liệu
Granitosaurus

2
Nó hiệu quả hơn nhiều so với bất kỳ giải pháp nào khác tôi từng thấy trên SO khi sử dụng kho trung gian của trình tải xuống khiến nó chỉ có một yêu cầu được thực hiện cho trang .. nếu nó quá khủng khiếp tại sao bạn không đưa ra giải pháp tốt hơn và chia sẻ thay vì tuyên bố trắng trợn một phía. "Không liên quan đến phế liệu" bạn đang hút thuốc gì à? Khác với việc thực hiện một số giải pháp phức tạp, mạnh mẽ và tùy chỉnh điên rồ, đây là cách tiếp cận mà tôi thấy hầu hết mọi người sử dụng. Điểm khác biệt duy nhất là phần lớn thực hiện phần selenium trong nhện khiến nhiều yêu cầu được thực hiện ...
rocktheartsm4l

10

Tôi đã sử dụng một phần mềm trung gian tải xuống tùy chỉnh, nhưng không hài lòng với nó, vì tôi đã không quản lý để làm cho bộ đệm hoạt động với nó.

Một cách tiếp cận tốt hơn là thực hiện một trình xử lý tải xuống tùy chỉnh.

Có một ví dụ làm việc ở đây . Nó trông như thế này:

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

Giả sử cái cạp của bạn được gọi là "cái cạp". Nếu bạn đặt mã được đề cập bên trong một tệp có tên handlers.py vào thư mục gốc của thư mục "cạp", thì bạn có thể thêm vào cài đặt của mình:

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

Và voilà, JS đã phân tích cú pháp DOM, với bộ đệm ẩn, thử lại, v.v.


Tôi thích giải pháp này!
rocktheartsm4l

Giải pháp tốt đẹp. Trình điều khiển Selenium vẫn là lựa chọn duy nhất?
Motheus

Giải pháp tuyệt vời. Cảm ơn rất nhiều.
CrazyGeek

4

Làm thế nào có thể sử dụng phế liệu để cạo dữ liệu động này để tôi có thể sử dụng nó?

Tôi tự hỏi tại sao không ai đăng giải pháp chỉ sử dụng Scrapy.

Kiểm tra bài đăng trên blog từ nhóm Scrapy SCRAPING INFINITE SCROLLING PAGES . Ví dụ về trang web http://spidyquotes.herokuapp.com/scroll sử dụng cuộn vô hạn.

Ý tưởng là sử dụng Công cụ dành cho nhà phát triển của trình duyệt của bạn và chú ý các yêu cầu AJAX, sau đó dựa trên thông tin đó để tạo các yêu cầu cho Phế liệu .

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)

Chúng ta lại phải đối mặt với cùng một vấn đề: Scrappy không được tạo ra cho mục đích này và đây là lúc chúng ta phải đối mặt với cùng một vấn đề. Chuyển sang ph PhantomJS hoặc như những người khác đề xuất, tạo phần mềm trung gian tải xuống của riêng bạn
rak007

@ rak007 PhantomJS vs trình điều khiển Chrome. Bạn muốn đề nghị cái nào?
Chankey Pathak

2

vâng, Scrapy có thể loại bỏ các trang web động, trang web được hiển thị thông qua javaScript.

Có hai cách tiếp cận để loại bỏ các loại trang web này.

Đầu tiên,

bạn có thể sử dụng splashđể kết xuất mã Javascript và sau đó phân tích HTML được kết xuất. bạn có thể tìm thấy tài liệu và dự án ở đây Scrapy Splash, git

Thứ hai,

Như mọi người đang nói, bằng cách theo dõi network calls, vâng, bạn có thể tìm thấy cuộc gọi api lấy dữ liệu và giả lập cuộc gọi trong con nhện phế liệu của bạn có thể giúp bạn có được dữ liệu mong muốn.


1

Tôi xử lý yêu cầu ajax bằng cách sử dụng Selenium và trình điều khiển web Firefox. Nó không phải là nhanh nếu bạn cần trình thu thập thông tin như một trình nền, nhưng tốt hơn nhiều so với bất kỳ giải pháp thủ công nào. Tôi đã viết một hướng dẫn ngắn ở đây để tham khảo

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.