引言------
在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载(ajax)的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值。
需求:爬取京东图书中的所有书籍信息。 (URL为https://book.jd.com/booksort.html)
需求分析:当我们进去该网站时,会发现当前页面展示的书籍分类信息是被动态加载出来的,如果直接通过程序对url进行请求,是获取不到动态加载出来的书籍分类信息的。但是我们发现:如果我们通过selenium进行访问就可以得到所有信息!所以咱们使用selenium实例化一个浏览器对象,在该对象中进行url的请求,获取动态加载的数据。
2.selenium在scrapy中使用的原理分析:
首先,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获取!
2.在观察进入某一书籍分类页面后:根据测试我们会发现,直接请求获取到的数据只有30本书籍的数据,但实际却有60本,可知页面中部分数据也是动态加载的,所以我们对此类页面数据的获取,使用selenium操作,并让页面执行js命令下滑至底部,即可获取完整的60条数据!
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中进行如下配置:
⑧settings.py中配置:
1.关闭君子协议:
2.开启下载延迟:(必开!)
3.配置日志:(将日志信息写入文件中,而不再在控制台输出!)
⑧管道保存数据:(编写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配置启用管道:
第二种:本地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配置启用管道:
3.settings.py配置连接本地MySql的信息: