- 拓展:scrapy如何进行图片数据爬取(ImagePipeLine)
- 中间件
- CrawlSpider
- 分布式
- 增量式
中间件
爬虫中间件
下载中间件
作用:批量拦截请求和响应
比如发送两个请求,返回了一个响应和一个异常
如何拦截请求和响应?
用middleware.py文件中的这三个方法:process_request、process_response、process_exception
- process_request: 请求(可以过滤)
- process_response: 响应
- process_exception: 异常
拦截请求干什么?
- 进行请求头信息的篡改==>需要写在process_request方法中
- - request.headers['User-Agent'] = 'xxx'
- 给请求设置代理==》process_exception
- - request.meta['proxy'] = 'https://ip:port'
- 拦截响应干什么?
- 篡改响应数据
中间件的作用
middleware.py,代码
# -*- coding: utf-8 -*-
# Define here the models for your spider middleware
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
from scrapy import signals
class MiddleproDownloaderMiddleware(object):
#拦截所有的请求
#参数:request就是拦截到的请求,spider就是爬虫类实例化好的对象
def process_request(self, request, spider):
print('i am process_request()')
request.headers['User-Agent'] = 'xxx'
#拦截所有的响应
#request就是拦截到响应对应的请求对象
def process_response(self, request, response, spider):
print('i am process_response()')
return response
#拦截发生异常的请求对象
def process_exception(self, request, exception, spider):
#为何需要拦截发生异常的请求?
#将拦截到异常的请求进行修正操作,修正成正常的请求对象后一定要对其进行重新发送
print('i am process_exception()')
#对异常的请求进行修正操作
request.meta['proxy'] = 'https://ip:port'
return request
settings.py,代码
DOWNLOADER_MIDDLEWARES = {
'middlePro.middlewares.MiddleproDownloaderMiddleware': 543,
}
例:网易案例,篡改response数据
爬虫文件,wangyi.py,代码
# -*- coding: utf-8 -*-
import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem
class WangyiSpider(scrapy.Spider):
name = 'wangyi'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://news.163.com/']
model_urls = [] #五个板块对应的url
#实例化好了浏览器对象
bro = webdriver.Chrome(executable_path=r'C:/Users/Administrator/Desktop/爬虫/chromedriver.exe')
#解析出五个板块对应的url
def parse(self, response):
#解析五个板块对应的url
li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
model_index = [3,4,6,7,8]
for index in model_index:
li_tag = li_list[index]
model_url = li_tag.xpath('./a/@href').extract_first()
self.model_urls.append(model_url)
#需要每一个板块的url发起请求(注意:板块url请求到的页面中有动态加载数据(新闻数据))
for url in self.model_urls:
yield scrapy.Request(url=url,callback=self.parse_model)
#用来解析每一个板块中的新闻数据(新闻的标题和新闻详情页的url)
def parse_model(self,response):
div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
for div in div_list:
title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
new_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
item = WangyiproItem()
item['title'] = title
if new_url:
#对新闻的详情页url发起请求
yield scrapy.Request(new_url,callback=self.parse_new,meta={'item':item})
#用来解析新闻内容
def parse_new(self,response):
item = response.meta['item']
content = response.xpath('//*[@id="content"]//text()').extract()
content = ''.join(content)
item['content'] = content
yield item
def closed(self,spider):
print('整个爬虫结束!')
self.bro.quit()
middleware.py,代码
# -*- coding: utf-8 -*-
# Define here the models for your spider middleware
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
from scrapy import signals
from time import sleep
from scrapy.http import HtmlResponse
class WangyiproDownloaderMiddleware(object):
def process_request(self, request, spider):
return None
def process_response(self, request, response, spider):
model_urls = spider.model_urls
bro = spider.bro
if request.url in model_urls:
#response就是我们想要拦截到的五个板块对应的响应对象
#将response的响应数据进行修改
bro.get(request.url)
sleep(2)
page_text = bro.page_source
return HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
else:
return response
def process_exception(self, request, exception, spider):
pass
CrawlSpider
- 作用:实现全栈数据爬取
- CrawlSpider其实就是Spider的一个子类
- 使用:
- 创建爬虫文件:scrapy genspider -t crawl spiderName http://www.xxx.com
就是可以用正则来定位所有的符合条件的标签
例:爬取阳光生活
http://wz.sun0769.com/political/index/politicsNewest?id=1&page=2
爬虫文件sun.py,代码
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4&page=']
#连接提取器:可以根据指定规则(allow=正则)进行连接的提取
link = LinkExtractor(allow=r'id=1&page=d+')
# link = LinkExtractor(allow=r'') 可以将网站中所有的连接都获取
rules = (
#规则解析器:接收link的连接且对其进行请求发送,然后根据指定形式的规则(callback)对其进行数据解析
Rule(link, callback='parse_item', follow=True),
)
def parse_item(self, response):
print(response)
middleware.py,代码
# -*- coding: utf-8 -*-
# Define here the models for your spider middleware
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
from scrapy import signals
from time import sleep
from scrapy.http import HtmlResponse
class WangyiproDownloaderMiddleware(object):
def process_request(self, request, spider):
return None
def process_response(self, request, response, spider):
model_urls = spider.model_urls
bro = spider.bro
if request.url in model_urls:
#response就是我们想要拦截到的五个板块对应的响应对象
#将response的响应数据进行修改
bro.get(request.url)
sleep(2)
page_text = bro.page_source
return HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
else:
return response
def process_exception(self, request, exception, spider):
pass
items.py,代码
import scrapy
class WangyiproItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
content = scrapy.Field()
pipeline.py,代码
class WangyiproPipeline(object):
def process_item(self, item, spider):
print(item)
return item
settings.py,代码
BOT_NAME = 'wangyiPro'
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
SPIDER_MODULES = ['wangyiPro.spiders']
NEWSPIDER_MODULE = 'wangyiPro.spiders'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
# 下载器
DOWNLOADER_MIDDLEWARES = {
'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}
# 管道
ITEM_PIPELINES = {
'wangyiPro.pipelines.WangyiproPipeline': 300,
}
分布式
- 概念:可以组建一个分布式的机群,然后让其联合且分布的对同一个资源进行数据爬取。
- 原生的scrapy框架无法实现分布式?
- scrapy无法实现调度器的共享
- scrapy无法实现管道的共享
- 如何实现分布式?
- 组件:scrapy-redis(手动安装)
- 作用:可以给scrapy提供可以被共享的管道和调度器
分布式的实现流程:
一、新建工程
二、cd 工程
三、新建爬虫文件(CrawlSpider) scrapy genspider -t crawl spiderName www.xxx.com
四、修改爬虫文件:
1.导包:from scrapy_redis.spiders import RedisCrawlSpider
2.将爬虫类的父类修改为RedisCrawlSpider
3.将start_url进行替换,替换成redis_key = ‘xxx’
4.实现后续的请求和解析操作
五、修改setting的配置文件
1.开启可以被共享的管道
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
2.开启可以被共享的调度器
# 使用scrapy-redis组件的去重队列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# True表示可以实现增量,False不可以实现增量
SCHEDULER_PERSIST = True
3.指定redis的host
REDIS_HOST = 'redis服务的ip地址'
REDIS_PORT = 6379
五、对redis的配置文件进行配置redis.windows.conf
- 56行:将默认绑定注释掉
- 75行:protected-mode no
六、启动redis的服务端和客户端
七、执行工程
八、向调度器的队列中扔入一个起始的url
- 需要在redis的客户端进行操作:
- lpush fbsQueue www.xxx.com
九、爬取到的数据全部存储到了redis的xxx:items这个数据结构中进行的存储
lpush是对redis里面列表类型进行添加操作
例:分布式爬虫
settings.py,代码
#开启可以被共享的管道
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
#开启可以被共享的调度器
# 使用scrapy-redis组件的去重队列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# True表示可以实现增量,False不可以实现增量
SCHEDULER_PERSIST = True
#指定redis
REDIS_HOST = '192.168.20.90'
REDIS_PORT = 6379
items.py,代码
import scrapy
class FbsproItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
爬虫文件,代码
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from fbsPro.items import FbsproItem
class FbsSpider(RedisCrawlSpider):
name = 'fbs'
# allowed_domains = ['www.xxx.com']
# start_urls = ['http://www.xxx.com/']
#表示的就是可以被共享的调度器队列的名称
redis_key = 'fbsQueue'
#提取页码连接
link = LinkExtractor(allow=r'page/d+.html')
rules = (
Rule(link, callback='parse_item', follow=True),
)
def parse_item(self, response):
li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
for li in li_list:
title = li.xpath('./div/a/@title').extract_first()
item = FbsproItem()
item['title'] = title
yield item
增量式
监测网站数据更新的情况,只爬取最新更新出来的数据。
原理:去重
实现:
- 记录表:可以将爬虫爬取过的信息记录在记录表中。以后再次爬取数据的时候,先在记录表中做查询,返回的结果如果是没有爬取过改数据,则进行爬取,否则跳过即可。
- 特性:记录表可以去重,且可以进行持久化存储。
- 因此redis的set集合是最为合适
- 特性:记录表可以去重,且可以进行持久化存储。
- 数据指纹:指的就是爬取过的一组数据的唯一标示。
例:
settings.py,代码
ITEM_PIPELINES = {
'zlsPro.pipelines.ZlsproPipeline': 300,
}
把管道解开注释
pipeline.py,代码
class ZlsproPipeline(object):
def process_item(self, item, spider):
conn = spider.conn
conn.lpush('movieData',item)
return item
items.py,代码
class ZlsproItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
desc = scrapy.Field()
爬虫文件
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
from zlsPro.items import ZlsproItem
class MovieSpider(CrawlSpider):
name = 'movie'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.4567kan.com/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/5/page/1.html']
link = LinkExtractor(allow=r'page/d+.html') #提取页码连接
conn = Redis(host='127.0.0.1',port=6379)
rules = (
Rule(link, callback='parse_item', follow=True),
)
def parse_item(self, response):
li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
for li in li_list:
title = li.xpath('./div/a/@title').extract_first()
detail_url = "https://www.4567kan.com"+li.xpath('./div/a/@href').extract_first()
item = ZlsproItem()
item['title'] = title
ex = self.conn.sadd("movie_urls",detail_url)
if ex == 1:#当前这个记录在记录表中不存在,可以爬取
print('有新数据更新,正在爬取......')
yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
else:
print('数据暂无更新!!!')
def parse_detail(self,response):
item = response.meta['item']
desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
item['desc'] = desc
yield item
反爬十种方法
- robots
- 不遵守robots协议,settings里面操作
- US检测
- 在settings.py里面加上UA检测,UA在network里面
- 动态加载数据
- 用抓包工具找ajax请求,requests发送请求,带上参数,头部....
- 图片懒加载
- 直接去解析图片的伪属性即可,只能应用于静态网站
- cookie
- 用session对象去cookie
- 验证码
- 超级鹰去解析,把验证码当成图片来保存在本地,然后用超级鹰去识别,返回一个字符串,把这个字符串当作验证码的参数传递到请求里面
- 点击验证码的话,可以用seleuim,去裁剪验证码,去超级鹰去识别,然后根据坐标去用动作链去操作。
- 动态变化的请求参数
- js逆向,去找原生的js生成动态参数d的请求方法
- 代理
- proxies参数 = random.randint(代理池列表)
- 代理池列表是列表套字典的形式,值可以去代理网站爬取
- 数据加密
- js逆向
- js混淆
- 用反混淆网站去做js处理,https://www.bm8.com.cn/jsConfusion/