1. 爬虫流程?
- 准备url列表
- 发起请求、获得响应
- 提取数据,提取url放入url列表
- 保存数据
聚焦爬虫的流程
注意:提取的数据以url对应的响应为准,浏览器element只能作为参考
2. requests的使用
`pip install requests`
2.1 基本使用
resp = request.get(url,headers,params)
resp = request.post(url,data,headers)
# 原始数据,bytes类型
resp.content
resp.content.decode()
# 根据响应信息进行有规律的推测网页的编码
resp.text
resp.encoding="utf-8"
2.2 保持会话
- session类
session = requests.sesssion() # Session()
session.get()
session.post()
# 每次请求后,会读取响应头的set——cookies,并在下次请求时自动携带
- Cookie字符串
从浏览器的请求头中复制一份Cookie
headers = {
Cookie:"xxxx"
}
requests.get(url,headers=headers)
- cookies参数
参数类型:dict
cookies_dict = {}
requests.get(url, headers=headers, cookies=cookies_dict)
2.3 设置UA,设置代理
headers = {
"User-Agent":"xxx"
}
requests.get(url, headers=headers)
proxies = {
"http":"http://192.168.1.1:80",
"http":"http://192.168.1.1:80"
}
requests.get(url, headers=headers, proxies=proxies)
3. xpath提取数据
`pip install lxml`
3.1 xpath语法
//a[@class='next'] 通过属性值定位标签
//a[text()='下一页'] 通过文本定位标签
//a[contains(@class,'next')] 定位class属性包含next的所有a标签
//a[not(@class or @name)] 定位所有不包含class属性和name属性的a标签
/div//text() 提取div下面的所有文本
/a/@href 提取属性值
/a/text() 提取文本值
/div/a div下面的a标签,a是div的子结点
/div//a div下面所有的a标签,a是div的后代结点
/a/follow-sibling::*[2] 获取a标签下面的所有兄弟结点的第二个
/a/follow-sibling::ul[1] 获取a标签下面所有ul的兄弟结点的第一个
3.2 lxml模块的使用
from lxml import etree
el = etree.HTML(str or bytes) # 参数可以是str或者bytes类型网页源代码
el.xpath("//a[@class='next']") # 返回是元素类型为element对象的列表
# element 具有xpath方法
el.xpath("//a/@href") # 返回元素类型为str的列表
4. scray框架
4.1 scrapy框架流程
- 调用start_requests()方法,将start_urls中所有的url构造成request对象,并放入调度器
- 引擎从调度器的请求队列中取出一个request,通过下载器中间件process_request()方法,交给下载器
- 下载器发起请求,获得响应,通过下载器中间件process_response()方法,到达引擎,再通过爬虫中间件的process_response()交给爬虫
- 爬虫提取数据
3.1 提取出来的是数据,通过引擎交给管道
3.2 提取出来的是url,构造请求,通过爬虫中间件的process_request()方法,交给调度器 - 管道进行数据清洗、数据保存
4.2 scrapy的基本使用
- scrapy startproject myspider
- cd myspider
- scrapy genspider (-t crawl) bd baidu.com
yield scrapy.Request(url,callback,meta,dont_filter) # url不会补全
# callback 将来url响应的处理函数
# dont_filter 默认false,过滤请求,重复的请求会被过滤
yield reponse.follow(url,....) # url会自动补全
def parse(self,repsonse):
item = response.meta['item']
response.xpath("").extract()
response.xpath("").extract_first()
4.3 管道
- 开启管道
在settting中,添加管道的路径
ITEM_PIPELINES = {
'suning.pipelines.SuningPipeline': 300, # 设置管道获取数据的优先级,数字越低,优先级越高
}
- 方法
process_item(item,spdier)
if spider.name = "itcast" # spider当前传递item的爬虫对象
item...
return item # 如果下一个管道需要数据,必须返回item
open_spdier(spider) # 每个爬虫开启的时候会执行一次
# 数据库的连接初始化
close_spdier(spider) # 每个爬虫关闭的时候执行一次
# 数据库的关闭
4.4 中间件
- 开启中间件
# SPIDER_MIDDLEWARES = {
# 'suning.middlewares.SuningSpiderMiddleware': 543,
# }
DOWNLOADER_MIDDLEWARES = {
'suning.middlewares.SeleniumMiddleware': 544,
}
- 中间件的两个方法
process_request(request,spider):
return None # 1. 继续请求,
return Request # 2. 请求不再继续,而是放入调度器
return Response # 3. 请求不再下载,交给爬虫提取数据
process_response(request,response,spdier):
return Request # 1. 请求放入调度器
return Response # 2. 继续经过其他中间件的process_response,或者到达引擎,后续交给爬虫处理
- 中间件的功能
- 设置UA
process_request(request,spider):
request.headers['User-Agent'] = random('UA')
- 设置代理
process_request(request,spider):
request.meta['proxy'] = 'http://192.168.1.1:80'
- 设置cookies(主要是为了反反爬)
process_request(request,spider):
request.cookies = cookies# 可以从cookies池随机取出一个
- scrpay集成selenium
open_spdier(spider):
if spider.name = "itcast":
spider.driver = webdirver.Chrome()
close_spider(spider);
if spider.name = "itcast":
spider.driver.quit()
process_request(request,spdier);
if spider.name = "itcast":
spdier.dirver.get(request.url)
return TextResponse(body=spdier.dirver.page_source,request=request,encoding="utf-8",url=spider.dirver.current_url)
4.5 post请求
scrapy.FormRequest(url,callback,formdata)
scrapy.FormRequest.from_response(response,fromdata,callback)
scrapy中默认开启的cookies传递,即本地请求获得的cookies,会在下次请求自动携带
# Disable cookies (enabled by default)
# COOKIES_ENABLED = False
- scrapy_redis
scrapy_redis 是scrapy框架的一个扩展组件,实现了两个功能:
- 增量式爬虫
- 分布式爬虫
实质:就是将请求队列和指纹集合进行了持久化存储
在seeeting.py中继续配置
# 指定了去重的类
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 制定了调度器的类
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 调度器的内容是否持久化
SCHEDULER_PERSIST = True
REDIS_URL = "redis://127.0.0.1:6379"
5.1 如何去重
1.请求生成指纹
fp = hashlib.sha1()
fp.update(to_bytes(request.method))
fp.update(to_bytes(canonicalize_url(request.url)))
fp.update(request.body or b'')
return fp.hexdigest()
利用hashlib的sha1,对request的请求体、请求url、请求方法进行加密,返回一个40位长度的16进制的字符串,称为指纹
- 进队
def enqueue_request(self, request):
if not request.dont_filter and self.df.request_seen(request):
self.df.log(request, self.spider)
return False
self.queue.push(request)
return True
- 如果请求需要过滤,并且当前请求的指纹已经在指纹集合中存在了,就不能进入队列了
- 如果不需要过滤,直接进入队列
- 如果请求需要过滤,并且请求的指纹是一个新的指纹,进入队列
5.2 实现分布式爬虫
类需要继承自 RedisSpider、RedisCrawlSpider
redis_key:表示,在redis数据库中存储start_urls的键的名称
-
数据去重
-
中间件去重
process_response(request,response,spider):
#set可以是内存set集合,也可以是redis的set
ret = set.add(md5(response.body))
if ret == 0:
return request
else
return response
- 建立复合索引
# 复合索引,加速和去重
stu.ensure_index([("hometown", 1), ("age", 1)], unique=True)
# 根据数据的特征,在mongodb中 对指定字段建立复合索引,所有字段值相同时就无法二次插入了
- 布隆过滤器