聚焦爬虫的流程
注意:提取的数据以url对应的响应为准,浏览器element只能作为参考
pip install requests
resp = request.get(url,headers,params)
resp = request.post(url,data,headers)
# 原始数据,bytes类型
resp.content
resp.content.decode()
# 根据响应信息进行有规律的推测网页的编码
resp.text
resp.encoding="utf-8"
session = requests.sesssion() # Session()
session.get()
session.post()
# 每次请求后,会读取响应头的set——cookies,并在下次请求时自动携带
headers = {
Cookie:"xxxx"
}
requests.get(url,headers=headers)
cookies_dict = {}
requests.get(url, headers=headers, cookies=cookies_dict)
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)
pip install lxml
//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的兄弟结点的第一个
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的列表
调用start_requests()方法,将start_urls中所有的url构造成request对象,并放入调度器
引擎从调度器的请求队列中取出一个request,通过下载器中间件process_request()方法,交给下载器
下载器发起请求,获得响应,通过下载器中间件process_response()方法,到达引擎,再通过爬虫中间件的process_response()交给爬虫
爬虫提取数据
3.1 提取出来的是数据,通过引擎交给管道
3.2 提取出来的是url,构造请求,通过爬虫中间件的process_request()方法,交给调度器
管道进行数据清洗、数据保存
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()
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) # 每个爬虫关闭的时候执行一次
# 数据库的关闭
# 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,或者到达引擎,后续交给爬虫处理
process_request(request,spider):
request.headers['User-Agent'] = random('UA')
process_request(request,spider):
request.meta['proxy'] = 'http://192.168.1.1:80'
process_request(request,spider):
request.cookies = cookies# 可以从cookies池随机取出一个
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)
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框架的一个扩展组件,实现了两个功能:
实质:就是将请求队列和指纹集合进行了持久化存储
在seeeting.py中继续配置
# 指定了去重的类
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 制定了调度器的类
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 调度器的内容是否持久化
SCHEDULER_PERSIST = True
REDIS_URL = "redis://127.0.0.1:6379"
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
类需要继承自 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中 对指定字段建立复合索引,所有字段值相同时就无法二次插入了
布隆过滤器
详情请查看
常见验证码,可以利用云打码平台去处理
利用selemiun破解极验滑动验证码
详情请参考
微博宫格验证码
这种验证码一般是频繁登陆后或者账号存在异常时才会显示
首先通过分析,这个宫格只有4个,最多的连接方式位24种,可以先把这24中图片全部下载到本地,根据灰色箭头的指向顺序分别命名这24张图片
利用selenium进行登陆时,如果弹出验证,可以把本次验证的图片下载到本地,和本地的24张图片进行匹配,设定只要99%的像素差值小于20的,认为两张图片匹配上了
获取匹配到的图片的文件名,即滑动的顺序确定
通过xpath找到4个按钮的element,根据滑动顺序,依次完成4个按钮的滑动即可
详情请参考
爬虫中为了获取登陆后页面的数据,必须携带对应的cookies,网站有时候也会根据账号的频繁请求断定当前是一个爬虫程序在请求,可能会进行限制,为了实现反反爬,需要构建cookies池,每次请求都携带不同cookies
存储形式:存储在redis中,“spider_name:username–password":cookie
需要专门创建一个py文件,包含四个方法:
在scrapy的下载器中间件(RetryMiddleware)继承自的process_request()随机选择一个cookie,进行设置,并在request的meta中保存该cookies对应的账号
def process_request(self,request,spider):
# 获取redis中所有的键(假设redis中只保存了cookies)
redisKeys = self.rconn.keys()
elem = random.choice(redisKeys)
request.cookies = cookie
# 在请求中记录当前cookies对应的账号和密码
request.meta["accountText"] = elem.split(":")[-1]
在中间件的process_response()中获取响应,如果响应状态码是301、302等,说明发生页面重定向,那么当前的这个cookies肯定失效了,需要更新或者删除cookies
def process_response(self,request,response,spider):
if response.status in [300, 301, 302, 303]:
# 获取重定向的url
redirect_url = response.headers["location"]
if url == "login_url":# 如果是登陆页面,说明当前cookies失效了,需要更新
username,passworod = request.meta['accountText'].split("--")
update_cookie(spider_name,username,password)
elif url=="验证页面":# 说明账号被封了
username,passworod = request.meta['accountText'].split("--")
remove_cookie(spider_name,username,password)
# RetryMiddleware中的尝试重新发起请求
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response # 重试
一般是mysql和mongodb
爬取的数据通常都是非结构化数据,这在关系模型的存储和查询上面都有很大的局限性。但爬回来的数据汇总处理后需要在网页上展示,此时可能需要和django项目对接,可以把爬虫数据组织成结构化数据,此时存储在mysql更好一些
根据数据量,200w到2000w的数据量相对来说不是很大,二者都可以。但是基本上数据库达到千万级别都会有查询性能的问 题,MYSQL单表在超过千万级以上性能表现不佳,所以如果数据持续增长的话,可以考虑用mongodb。毕竟mongodb分片集群搭建起来比mysql集群简单多 了,而且处理起来更灵活