使用Scrapy + Selenium + PhantopJS抓取数据丢失了数据

时间:2017-10-02 10:34:14

标签: python ajax scrapy web-crawler scrapy-spider

我尝试抓取来自http://www.sse.com.cn/assortment/stock/list/share/的表格数据,这是 AJAX 页面。我的代码如下:

import scrapy

class GovSpider(scrapy.Spider):
    name = 'gov'

    url = "http://www.sse.com.cn/assortment/stock/list/share/"

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
    }

    driver = webdriver.PhantomJS('/Users/luozhongjin/ScrapyDemo/ScrapyDemo/phantomjs')
    driver.implicitly_wait(15)

    def start_requests(self):
        yield scrapy.Request(url = self.url, headers = self.headers,callback = self.parse);

    def parse(self, response):
        self.driver.get(response.url)
        self.driver.set_window_size(1124, 850)
        i = 1
        while True:
            soup = BeautifulSoup(self.driver.page_source, 'lxml')
            trs = soup.findAll("tr")
            for tr in trs:
                try:
                    tds = tr.findAll("td")
                    print tds
                    item = GovSpiderItem()
                    item["name"] = tds[1].string
                    print ("ok")
                    yield item
                except:
                    pass
            try:
                next_page = self.driver.find_element_by_class_name("glyphicon-menu-right").click()
                i = i + 1
                if i >= 55:
                    break
            except:
                break

但是当它完成后,我检查了json文件并发现它丢失了数据,也就是说,我需要所有54页数据,但它有时只保存53页数据,有时52页数据甚至更少在我的不同测试中。但我添加了一行

time.sleep(3)

在解析函数的while循环结束时,它可以工作。但我不知道为什么会这样。我想这可能是ajax请求没有完成没有时间延迟,这导致数据丢失。所以我添加了以下测试线

WebDriverWait(self.driver, 10).until(lambda driver: self.driver.execute_script("return jQuery.active == 0"))

此行用于等待ajax请求完成。但它没有用。有人能告诉我为什么我丢失了数据吗?是否有一种简单的方法可以使用Scrapy抓取ajax页面。

1 个答案:

答案 0 :(得分:1)

jQuery.active是当前AJAX请求的数量。因此驱动程序将等待ajax请求完成。但是需要一些时间来解析响应并呈现数据。

ajax complete -> render the data -> html source updated

如果驱动程序在渲染完成之前尝试获取源,则会丢失一些数据。我会选择一个条件来检查元素值。在这里,我保持当前最大库存ID,并且由于所有数据都按升序排列,因此新数据必须大于它:

return current_max_id < parseInt(document.getElementsByTagName("td")[0].children[0].text);

数据丢失的另一个可能原因是driver.implicitly_wait(15)可能无法正常运行,如文档中所述:

  

隐式等待告诉WebDriver轮询DOM一定数量   尝试不立即找到任何元素(或元素)的时间   可用。默认设置为0.设置后,隐式等待为   为WebDriver对象的生命设置。

在这里,您将driver.page_source投放到BeautifulSoup而不是driver.find_xxx,因此driver.implicitly_wait(15)不会被触发,它可能会跳过第1页。这里我将使用另一个要检查的条件:

return document.getElementsByTagName("td").length > 0;

测试代码:

import scrapy
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait


class GovSpider(scrapy.Spider):
    name = 'gov'

    url = "http://www.sse.com.cn/assortment/stock/list/share/"

    driver = webdriver.Chrome()
    driver.set_window_size(1124, 850)

    def start_requests(self):
        yield scrapy.Request(url=self.url, callback=self.parse)

    def parse(self, response):
        i = 1
        current_max = 0

        self.driver.get(response.url)
        WebDriverWait(self.driver, 10).until(
            lambda driver: self.driver.execute_script('return document.getElementsByTagName("td").length > 0;'))

        while True:
            soup = BeautifulSoup(self.driver.page_source, 'lxml')
            trs = soup.findAll("tr")
            for tr in trs:
                try:
                    tds = tr.findAll("td")
                    stock_id = int(tds[0].string)
                    current_max = max(current_max, stock_id)
                    yield {
                        'page num': i,
                        'stock id': tds[0].string
                    }
                except:
                    pass
            try:
                self.driver.find_element_by_class_name("glyphicon-menu-right").click()

                js_condition_tpl = 'return {} < parseInt(document.getElementsByTagName("td")[0].children[0].text);'
                WebDriverWait(self.driver, 10).until(
                    lambda driver: self.driver.execute_script(js_condition_tpl.format(current_max)))

                i = i + 1
                if i >= 55:
                    break
            except:
                break  

PS:如果您只需要数据本身,页面中有一个xls下载链接,这是一种更健壮,更简便的数据获取方式。