Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

引言------
在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载(ajax)的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值。

1.案例分析:
需求:爬取京东图书中的所有书籍信息。  (URL为https://book.jd.com/booksort.html)

需求分析:当我们进去该网站时,会发现当前页面展示的书籍分类信息是被动态加载出来的,如果直接通过程序对url进行请求,是获取不到动态加载出来的书籍分类信息的。但是我们发现:如果我们通过selenium进行访问就可以得到所有信息!所以咱们使用selenium实例化一个浏览器对象,在该对象中进行url的请求,获取动态加载的数据。
2.selenium在scrapy中使用的原理分析:

Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

首先,Scrapy运行流程:

    1.爬虫中起始的url构造的url对象-->爬虫中间件-->引擎-->调度器
    2.调度器把request-->引擎-->下载中间件-->下载器
    3.下载器发送请求,获取response响应--->下载中间件--->引擎-->爬虫中间件--->爬虫
    4.爬虫提取url地址,组装成request对象--->爬虫中间件--->引擎--->调度器,重复步骤2
    5.爬虫提取数据--->引擎--->管道处理和保存数据
爬虫中间件和下载中间件只是运行的逻辑的位置不同,作用是重复的:如替换UA等

然后,分析:

当引擎将京东图书的url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据封装到response中,提交给引擎,引擎将response在转交给Spiders。Spiders接受到的response对象中存储的页面数据里是没有动态加载的数据的。要想获取动态加载的数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,且对其内部存储的页面数据进行篡改,修改成携带了动态加载出的数据的页面数据,然后将篡改后的response对象最终交给Spiders进行解析操作。

3.selenium在scrapy中的使用流程:
1.重写爬虫文件的构造方法,在该方法中使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次)。
2.重写爬虫文件的closed(self,spider)方法,在其内部关闭浏览器对象。该方法是在爬虫结束时自动被调用。
3.重写下载中间件的process_response方法,让该方法对响应对象进行拦截,并篡改response中存储的页面数据(使其包含动态加载的数据)。
4.在配置文件中开启下载中间件。
4.实操-------Scrapy结合使用selenium爬取京东图书信息(ajax动态加载的数据)

①创建爬虫项目:

cd到存放此爬虫项目的目录下,输入以下命令:

scrapy startproject jd

②创建爬虫文件:

cd到爬虫项目目录下,输入以下命令:

scrapy genspider book book.jd.com

③编写爬虫文件:(本爬虫文件爬取数据:京东图书所有小分类里第一页60本书籍信息!当然,你可以通过简单的编写实现翻页,以达到获取所有数据的目的!我就不搞了!)

# -*- coding: utf-8 -*-
import json
from selenium import webdriver
import scrapy

from ..items import JdItem


class BookSpider(scrapy.Spider):
    name = 'book_test'
    # 1.检查域名
    allowed_domains = ['book.jd.com']
    # 2.修改起始的url
    start_urls = ['https://book.jd.com/booksort.html']

    def __init__(self):
        # 实例化一个浏览器对象(实例化一次)   开启无头模式
        options = webdriver.ChromeOptions()
        options.add_argument("--headless")
        options.add_argument("--disable-gpu")
        self.driver = webdriver.Chrome(options=options, executable_path="C:\my\Chrome_guge\chromedriver.exe")


    def closed(self, spider):
        # 必须在整个爬虫结束后,关闭浏览器
        print('爬虫结束!')
        self.driver.quit()

    def parse(self, response):
        # 3.获取所有图书大分类节点列表
        big_node_list = response.xpath('//*[@id="booksort"]/div[2]/dl/dt/a')
        for big_node in big_node_list:
            big_category = big_node.xpath('./text()').extract_first()
            big_category_link = response.urljoin(big_node.xpath('./@href').extract_first())
            # 获取所有图书小分类节点列表
            # following-sibling 选取当前节点之后的所有同级节点。   此处:选取当前节点(dt)之后的第一个同级dd节点下的em下的a
            small_node_list = big_node.xpath('../following-sibling::dd[1]/em/a')
            for small_node in small_node_list:
                temp = dict()
                temp['big_category'] = big_category
                temp['big_category_link'] = big_category_link
                temp['small_category'] = small_node.xpath('./text()').extract_first()
                temp['small_category_link'] = response.urljoin(small_node.xpath('./@href').extract_first())
                # 模拟点击小分类链接
                yield scrapy.Request(
                    url=temp['small_category_link'],
                    callback=self.parse_book_link,
                    meta={"py21": temp},
                    dont_filter=True
                )

    def parse_book_link(self, response):
        pass
        # 图书页
        temp = response.meta["py21"]
        book_list = response.xpath('//*[@id="J_goodsList"]/ul/li/div')
        for book in book_list:
            item = JdItem()
            # item.update(temp)         # 试试可以代替下面四句不!
            item['big_category'] = temp['big_category']
            item['big_category_link'] = temp['big_category_link']
            item['small_category'] = temp['small_category']
            item['small_category_link'] = temp['small_category_link']

            item['book_name'] = book.xpath('//*[@id="J_goodsList"]/ul/li/div/div[3]/a/em/text()').extract_first().strip()
            item['book_link'] = 'https://item.jd.com' + book.xpath('//*[@id="J_goodsList"]/ul/li/div/div[3]/a/@href').extract_first()
            item['book_price'] = book.xpath('//*[@id="J_goodsList"]/ul/li/div/div[2]/strong/i/text()').extract_first()
            item['book_pic_link'] = 'https://item.jd.com' + book.xpath('//*[@id="J_goodsList"]/ul/li/div/div[1]/a/img/@src').extract_first()
            print(item)
            yield item

④编写下载中间件文件:

1.因为根据观察即测试可知:京东图书首页书籍分类数据都是动态(ajax)加载的,直接请求获取不到数据,所以对于京东图书首页书籍分类数据的获取(即start_url),我们使用selenium获取!
Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

2.在观察进入某一书籍分类页面后:根据测试我们会发现,直接请求获取到的数据只有30本书籍的数据,但实际却有60本,可知页面中部分数据也是动态加载的,所以我们对此类页面数据的获取,使用selenium操作,并让页面执行js命令下滑至底部,即可获取完整的60条数据!

Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

from scrapy.http import HtmlResponse

class JdDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.
	# 只需重写下载中间件的process_response()方法即可:
    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.
        '''
        参数介绍:
        拦截到响应对象(下载器传递给Spider的响应对象)
        request:响应对象对应的请求对象
        response:拦截到的响应对象
        spider:爬虫文件中对应的爬虫类的实例
        :param request:
        :param response:
        :param spider:
        :return:
        '''
        # 响应对象中存储页面数据的篡改
        if request.url == 'https://book.jd.com/booksort.html':   
            spider.driver.get(url=request.url)
            time.sleep(2)
            # 一定要给予浏览器一定的缓冲加载数据的时间
            # 页面数据就是包含了动态加载出来的数据对应页面数据
            page_text = spider.driver.page_source
            # 篡改响应对象
            return HtmlResponse(url=spider.driver.current_url,body=page_text,encoding='utf-8',request=request)
        elif 'https://list.jd.com/list.html?cat=' in request.url:
            spider.driver.get(url=request.url)
            js = 'window.scrollTo(0,{})'.format(8000)  # js语句
            spider.driver.execute_script(js)           # 执行js的方法
            time.sleep(2)
            page_text = spider.driver.page_source
            return HtmlResponse(url=spider.driver.current_url,body=page_text,encoding='utf-8',request=request)
        else:
            return response

⑤配置开启下载中间件:

# settings.py文件里配置:
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
   'jd.middlewares.JdDownloaderMiddleware': 543,
}

⑥拓展:通过编写中间件实现每次请求使用随机UA:

1.编写随机UA中间件:

# 在middlewares.py文件中加入以下中间件类:
#UA池
from .settings import user_agent_list
import random
class User_AgentDownloaderMiddleware(object):
    def process_request(self, request, spider):
        #在Request中添加请求头
        request.headers["User-Agent"]=random.choice(user_agent_list) #随机选择一个UA
        return None

2.settings.py文件中俩配置:

# 1.settings.py文件中对应位置开启随机UA中间件;
# 2.settings.py文件中加入准备好的UA:
user_agent_list=[
    "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.29 Safari/525.13",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/531.4 (KHTML, like Gecko) Chrome/3.0.194.0 Safari/531.4",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.50 Safari/525.19",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.7 Safari/532.0",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; Lunascape 5.0 alpha2)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.7 Safari/532.2",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.195.10 Safari/532.0",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Maxthon;",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/530.1 (KHTML, like Gecko) Chrome/2.0.169.0 Safari/530.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP; rv:1.7) Gecko/20040614 Firefox/0.9",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.0 Safari/532.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.4.3.4000 Chrome/30.0.1599.101 Safari/537.36",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.500.0 Safari/534.6",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; TencentTraveler)",
    "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.4 (KHTML, like Gecko) Chrome/6.0.481.0 Safari/534.4",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.370.0 Safari/533.4",
    "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0",
    "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.31 Safari/525.19",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.1.17) Gecko/20110123 (like Firefox/3.x) SeaMonkey/2.0.12",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE) Chrome/4.0.223.3 Safari/532.2",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24",
    "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.42 Safari/525.19",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.3 (KHTML, like Gecko) Chrome/4.0.227.0 Safari/532.3",
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.8",
    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.460.0 Safari/534.3",
    "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.463.0 Safari/534.3",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/528.9 (KHTML, like Gecko) Chrome/2.0.157.0 Safari/528.9",
    "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1",
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.694.0 Safari/534.24",
    "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
    "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50",
    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20120427 Firefox/15.0a1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
]

⑦拓展:通过编写中间件实现IP池中使用随机IP:

本人使用的是webapi,直接连接至快代理中的隧道代理!操作简单。只需在中间件中加入以下代码,并在快代理中将你本机外网IP加入白名单即可直接使用!

# 在middlewares.py文件中加入以下中间件即可:
#IP池
from scrapy import signals
from w3lib.http import basic_auth_header
class ProxyDownloaderMiddleware:
    def process_request(self, request, spider):
        proxy = "tps191.kdlapi.com:15818"
        request.meta['proxy'] = "http://%(proxy)s" % {'proxy': proxy}
        # 用户名密码认证
        # request.headers['Proxy-Authorization'] = basic_auth_header('${username}', '${password}')  # 白名单认证可注释此行
        return None

注意:如果出现Bug,或者没出你也为了预防,在settings.py中进行如下配置:
Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

⑧settings.py中配置:

1.关闭君子协议:

Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

2.开启下载延迟:(必开!)
Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!3.配置日志:(将日志信息写入文件中,而不再在控制台输出!)
Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

⑧管道保存数据:(编写items.py文件和pipelines.py文件)

第一部分:编写items.py文件(结构化字段)

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class JdItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    big_category = scrapy.Field()
    big_category_link = scrapy.Field()

    small_category = scrapy.Field()
    small_category_link = scrapy.Field()

    book_name = scrapy.Field()
    book_link = scrapy.Field()
    book_price = scrapy.Field()
    book_pic_link = scrapy.Field()

第二部分:编写pipelines.py文件(进行数据持久化的处理)

第一种:本地文件保存!

1.在pipelines.py文件中编写如下:

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import json
import pymysql


class JdPipeline(object):
    def __init__(self):
        self.file = open('book.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        # 将item对象强制转为字典,该操作只能在scrapy中使用
        item = dict(item)
        # 爬虫文件中提取数据的方法每yield一次,就会运行一次
        # 该方法为固定名称函数
        # 默认使用完管道,需要将数据返回给引擎
        # 1.将字典数据序列化
        '''ensure_ascii=False 将unicode类型转化为str类型,默认为True'''
        json_data = json.dumps(item, ensure_ascii=False, indent=2) + ',\n'

        # 2.将数据写入文件
        self.file.write(json_data)

        return item

    def __del__(self):
        self.file.close()

2.settings.py配置启用管道:

Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

第二种:本地MySql数据库保存!(注意:使用之前一定要创建对应的数据库和数据表!)

1.在pipelines.py文件中加入如下代码:

class JDSqlPipeline(object):
    # 1.连接数据库
    def open_spider(self,spider):
        data_config=spider.settings["DATABASE_CONFIG"]
        if data_config["type"]=="mysql":
            self.conn=pymysql.connect(**data_config["config"])
            self.cursor=self.conn.cursor()

    def process_item(self,item,spider):
        dict(item)
        sql='insert into JD_book (big_category, big_category_link, small_category, small_category_link, book_name, book_link, book_price, book_pic_link) values(%s,%s,%s,%s,%s,%s,%s,%s)'
        self.cursor.execute(sql,
                                (
                                    item["big_category"],
                                    item["big_category_link"],
                                    item["small_category"],
                                    item["small_category_link"],
                                    item["book_name"],
                                    item["book_link"],
                                    item["book_price"],
                                    item["book_pic_link"]
                                )
                            )
        self.conn.commit()
        return item
    def close_spider(self,spider):
        self.cursor.close()
        self.conn.close()

2.settings.py配置启用管道:

Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!
3.settings.py配置连接本地MySql的信息:
Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

运行很完美!!!(本地和MySql数据库中数据都正常。)

Scrapy中selenium的应用-----并通过京东图书书籍信息爬取项目进行实操!

上一篇:Scrapy实战-文章爬取


下一篇:基于python的凤凰网网络爬虫设计开题报告