- 爬虫
- 1. 什么是爬虫 :
- 2. 爬虫的分类 :
- 3. 爬虫安全性的探究
- 4. http & https
- 5. request模块
- 6. 数据解析
- 7. 代理
- 8. cookie
- 9. 验证码的识别
- 10. 使用线程池提升爬取数据的效率
- 11. 单线程+多任务的异步协程
- 12. selenium
- 13. 12306模拟登陆
- 14. 空气质量数据爬取
- 15. scrapy爬虫框架
- 16. scrapy的图片数据爬取(数据流的爬取)
- 17. 在scrapy中如何进行手动请求发送
- 18. 在scrapy中如何提升爬取数据的效率?
- 19. 请求传参 + 核心组件
- 20. scrapy的中间件
- 21. CrawlSpider实现的全站数据的爬取
- 22. 基于分布式的爬虫
- 23. 基于Celery的分布式/Pypperteer(异步的scrapy)
- 24. 增量式
- 25. 反爬机制
爬虫
启动:jupyter notebook
介绍:
anaconda是一个集成环境(数据分析+机器学习)
提供了一个叫做jupyter的可视化工具(基于浏览器)
jupyter的基本使用
快捷键:
插入cell:a,b
删除:x
执行:shift+enter
切换cell的模式:y,m
tab:自动补全
打开帮助文档:shift+tab
1. 什么是爬虫 :
- 通过编写程序模拟浏览器上网,从互联网中爬取需要的数据的过程
2. 爬虫的分类 :
- 通用爬虫 : 爬取一整张页面源码数据.搜索引擎 (抓取系统→内部封好的一套爬虫程序) 重点使用的是该种形式爬虫
- 聚焦爬虫 : 抓取页面中指定的局部数据
- 增量式爬虫 : 监测网站数据更新的情况.抓取网站最新更新的数据
3. 爬虫安全性的探究
-
风险所在
- 爬虫干扰了被访问网站的正常运营;
- 爬虫抓取了受到法律保护的特定类型的数据或信息
-
如何规避风险
- 严格遵守网站设置的robots协议;
- 在规避反爬虫措施的同时,需要优化自己的代码,避免干扰被访问网站的正常运行;
- 在使用、传播抓取到的信息时,应审查所抓取的内容,如发现属于用户的个人信息、隐私或者他人的商业秘密的,应及时停止并删除
爬虫机制 :应用在网站中
反反爬机制 : 应用在爬虫程序中
第一个反爬机制 :
robots协议:纯文本的协议
- 特点:防君子不防小人
4. http & https
- 什么是http协议
- 服务器和客户端进行数据交互的某种形式
- https - 安全 (数据加密) 的http协议
头部信息
1、通用头部
通用头域包含请求和响应消息都支持的头域。
Request URL:请求的URL地址
Request Method: 请求方法,get/post/put/……
Status Code:状态码,200 为请求成功
Remote Address:路由地址
2、请求头部
1) Accept: 告诉WEB服务器自己接受什么介质类型,*/* 表示任何类型,type/* 表示该类型下的所有子类型;
2)Accept-Charset: 浏览器申明自己接收的字符集
Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip, deflate)
3)Accept-Language: 浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等。
4)Authorization: 当客户端接收到来自WEB服务器的 WWW-Authenticate 响应时,该头部来回应自己的身份验证信息给WEB服务器。
5)Connection:表示是否需要持久连接。close(告诉WEB服务器或者代理服务器,在完成本次请求的响应后,断开连接,
不要等待本次连接的后续请求了)。keep-alive(告诉WEB服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)。
6)Referer:发送请求页面URL。浏览器向 WEB 服务器表明自己是从哪个 网页/URL 获得/点击 当前请求中的网址/URL。
7)User-Agent: 浏览器表明自己的身份(是哪种浏览器)。
8)Host: 发送请求页面所在域。
9)Cache-Control:浏览器应遵循的缓存机制。
no-cache(不要缓存的实体,要求现在从WEB服务器去取)
max-age:(只接受 Age 值小于 max-age 值,并且没有过期的对象)
max-stale:(可以接受过去的对象,但是过期时间必须小于 max-stale 值)
min-fresh:(接受其新鲜生命期大于其当前 Age 跟 min-fresh 值之和的缓存对象)
10)Pramga:主要使用 Pramga: no-cache,相当于 Cache-Control: no-cache。
11)Range:浏览器(比如 Flashget 多线程下载时)告诉 WEB 服务器自己想取对象的哪部分。
12)Form:一种请求头标,给定控制用户代理的人工用户的电子邮件地址。
13)Cookie:这是最重要的请求头信息之一
3、响应头部
1)Age:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。
2)Accept-Ranges:WEB服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。
3) Cache-Control:服务器应遵循的缓存机制。
public(可以用 Cached 内容回应任何用户)
private(只能用缓存内容回应先前请求该内容的那个用户)
no-cache(可以缓存,但是只有在跟WEB服务器验证了其有效后,才能返回给客户端)
max-age:(本响应包含的对象的过期时间)
ALL: no-store(不允许缓存)
4) Connection: 是否需要持久连接
close(连接已经关闭)。
keepalive(连接保持着,在等待本次连接的后续请求)。
Keep-Alive:如果浏览器请求保持连接,则该头部表明希望 WEB 服务器保持连接多长时间(秒)。例如:Keep- Alive:300
5)Content-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。 例如:Content-Encoding:gzip
6)Content-Language:WEB 服务器告诉浏览器自己响应的对象的语言。
7)Content-Length:WEB 服务器告诉浏览器自己响应的对象的长度。例如:Content-Length: 26012
8)Content-Range:WEB 服务器表明该响应包含的部分对象为整个对象的哪个部分。例如:Content-Range: bytes 21010-47021/47022
9)Content-Type:WEB 服务器告诉浏览器自己响应的对象的类型。例如:Content-Type:application/xml
10)Expired:WEB服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。
11) Last-Modified:WEB 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。
12) Location:WEB 服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。
13)Proxy-Authenticate: 代理服务器响应浏览器,要求其提供代理身份验证信息。
14)Server: WEB 服务器表明自己是什么软件及版本等信息。
15)Refresh:表示浏览器应该在多少时间之后刷新文档,以秒计。
https的加密方式
对称密钥加密
非对称密钥加密
证书密钥加密
5. request模块
基于网络请求的python模块
作用 :模拟浏览器发送请求,实现爬虫
环境安装 : pip install request
编码流程 :
- 指定url
- 发起请求
- 获取响应数据
- 持久化存储
1. 爬取搜狗首页的页面源码数据
import requests
#1.指定url
url = 'https://www.sogou.com/'
#2.请求发送:get返回的是一个响应对象
response = requests.get(url=url)
#3.获取响应数据:text返回的是字符串形式的响应数据
page_text = response.text
#4.持久化存储
with open('./sogou.html','w',encoding='utf-8') as fp:
fp.write(page_text)
2. 实现一个简易的网页采集器
请求参数的动态化
url = 'https://www.sogou.com/web'
#请求参数的动态化
wd = input('enter a key word:')
params = {
'query':wd
}
response = requests.get(url=url,params=params)
page_text = response.text
fileName = wd+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_text)
print(fileName,'爬取成功!')
上述代码问题:
- 乱码问题
- response.encoding = 'xxx'
- 数据丢失
- 反爬机制:UA检测
- 反反爬策略:UA伪装
#乱码问题的解决
url = 'https://www.sogou.com/web'
#请求参数的动态化
wd = input('enter a key word:')
params = {
'query':wd
}
response = requests.get(url=url,params=params)
#将响应数据的编码格式手动进行指定
response.encoding = 'utf-8'
page_text = response.text
fileName = wd+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_text)
print(fileName,'爬取成功!')
#UA伪装操作
url = 'https://www.sogou.com/web'
#请求参数的动态化
wd = input('enter a key word:')
params = {
'query':wd
}
#UA伪装
headers = {
'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'
}
response = requests.get(url=url,params=params,headers=headers)
#将响应数据的编码格式手动进行指定
response.encoding = 'utf-8'
page_text = response.text
fileName = wd+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_text)
print(fileName,'爬取成功!')
3. 动态加载的数据
通过另一个网络请求 (ajax) 请求到的数据
爬取豆瓣电影中动态加载出的电影详情数据 :
url = 'https://movie.douban.com/j/chart/top_list'
#参数动态化
params = {
'type': '17',
'interval_id': '100:90',
'action': '',
'start': '0',
'limit': '200',
}
response = requests.get(url=url,params=params,headers=headers)
#json()返回的是序列化好的对象
movie_list = response.json()
for movie in movie_list:
print(movie['title'],movie['score'])
总结:对一个陌生的网站进行数据爬取的时候,首先确定的一点就是爬取的数据是否为动态加载出来的
是:需要通过抓包工具捕获到动态加载数据对应的数据包,从中提取出url和请求参数。
不是:直接对浏览器地址栏的url发起请求即可
如何检测爬取的数据是不是动态加载出来的?
通过抓包工具进行局部搜索就可以验证数据是否为动态加载
搜索到:不是动态加载
搜索不到:是动态加载
如何定位动态加载的数据在哪呢?
通过抓包工具进行全局搜索进行定位
4. 爬取肯德基的餐厅位置信息
http://www.kfc.com.cn/kfccda/storelist/index.aspx
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
data = {
'cname': '',
'pid': '',
'keyword': '上海',
'pageIndex': '1',
'pageSize': '10',
}
address_dic = requests.post(url=url,data=data,headers=headers).json()
for dic in address_dic['Table1']:
print(dic['addressDetail'])
5. 面试题
- 需求
https://www.fjggfw.gov.cn/Website/JYXXNew.aspx 福建省公共资源交易中心
提取内容:
工程建设中的中标结果信息/中标候选人信息
1. 完整的html中标信息
2. 第一中标候选人
3. 中标金额
4. 中标时间
5. 其它参与投标的公司
- 实现思路
- 确认爬取的数据都是动态加载出来的
- 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数
- 对提取到的url进行请求发送,获取响应数据(json)
- 从json串中提取到每一个公告对应的id值
- 将id值和中标信息对应的url进行整合,进行请求发送捕获到每一个公告对应的中标信息数据
post_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx'
headers = {
'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',
'Cookie': '_qddac=4-3-1.4euvh3.x4wulp.k1hj8mnw; ASP.NET_SessionId=o4xkycpib3ry5rzkvfcamxzk; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570520304; __root_domain_v=.fjggfw.gov.cn; _qddaz=QD.89mfu7.7kgq8w.k1hj8mhg; _qdda=4-1.4euvh3; _qddab=4-x4wulp.k1hj8mnw; _qddamta_2852155767=4-0; _qddagsx_02095bad0b=2882f90558bd014d97adf2d81c54875229141367446ccfed2b0c8913707c606ccf30ec99a338fed545821a5ff0476fd6332b8721c380e9dfb75dcc00600350b31d85d17d284bb5d6713a887ee73fa35c32b7350c9909379a8d9f728ac0c902e470cb5894c901c4176ada8a81e2ae1a7348ae5da6ff97dfb43a23c6c46ec8ec10; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570520973'
}
data = {
'OPtype': 'GetListNew',
'pageNo': '1',
'pageSize': '10',
'proArea': '-1',
'category': 'GCJS',
'announcementType': '-1',
'ProType': '-1',
'xmlx': '-1',
'projectName': '',
'TopTime': '2019-07-10 00:00:00',
'EndTime': '2019-10-08 23:59:59',
'rrr': '0.7293828344656237',
}
post_data = requests.post(url=post_url,headers=headers,data=data).json()
for dic in post_data['data']:
_id = int(dic['M_ID'])
detail_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx?OPtype=GetGGInfoPC&ID={}&GGTYPE=5&url=AjaxHandler%2FBuilderHandler.ashx'.format(_id)
company_data = requests.get(url=detail_url,headers=headers).json()['data']
company_str = ''.join(company_data)
print(company_str)
6. 数据解析
1. 如何爬取图片数据?
- 基于requests|
- 基于urllib
- 区别:urllib中的urlretrieve不可以进行UA伪装
import requests
headers = {
'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'
}
#基于requests的图片爬取
url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
img_data = requests.get(url=url,headers=headers).content #content返回的是byte类型的响应数据
with open('./123.jpg','wb') as fp:
fp.write(img_data)
#基于urllib的图片爬取
from urllib import request
url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
request.urlretrieve(url,'./456.jpg')
2. 数据解析
数据解析
-
概念:将一整张页面中的局部数据进行提取/解析
-
作用:用来实现聚焦爬虫的吧
-
实现方式:
- 正则
- bs4
- xpath
- pyquery
-
数据解析的通用原理是什么?
- 标签的定位
- 数据的提取
-
页面中的相关的字符串的数据都存储在哪里呢?
- 标签中间
- 标签的属性中
-
- 基于聚焦爬虫的编码流程 - 指定url - 发起请求 - 获取响应数据 - 数据解析 - 持久化存储
正则解析
- 将煎蛋网中的图片数据进行爬取且存储在本地 :
import re
import os
dirName = './imgLibs'
if not os.path.exists(dirName):
os.mkdir(dirName)
url = 'http://jandan.net/pic/MjAxOTEwMDktNjY=#comments'
page_text = requests.get(url,headers=headers).text
#解析数据:img标签的src的属性值
ex = '<div class="text">.*?<img src="(.*?)" referrerPolicy.*?</div>'
img_src_list = re.findall(ex,page_text,re.S)
for src in img_src_list:
if 'org_src' in src:
src = re.findall('org_src="(.*?)" onload',src)[0]
src = 'http:'+src
imgName = src.split('/')[-1]
imgPath = dirName+'/'+imgName
request.urlretrieve(src,imgPath)
print(imgName,'下载成功!!!')
bs4解析
-
- 环境的安装: - pip install bs4 - pip install lxml - bs4的解析原理: - 实例化一个BeautifulSoup的一个对象,把即将被解析的页面源码数据加载到该对象中 - 需要调用BeautifulSoup对象中的相关的方法和属性进行标签定位和数据的提取 - BeautifulSoup的实例化 - BeautifulSoup(fp,'lxml'):将本地存储的html文档中的页面源码数据加载到该对象中 - BeautifulSoup(page_text,'lxml'):将从互联网中请求道的页面源码数据加载到改对象中 - 标签的定位 - soup.tagName:只可以定位到第一个tagName标签 - 属性定位:soup.find('tagName',attrName='value'),只可以定位到符合要求的第一个标签 - findAll:返回值是一个列表。可以定位到符合要求的所有标签 - 选择器定位:soup.select('选择器') - 选择器:id,class,tag,层级选择器(大于号表示一个层级,空格表示多个层级) - 取文本 - text:将标签中所有的文本取出 - string:将标签中直系的文本取出 - 取属性 - tag['attrName']
from bs4 import BeautifulSoup
fp = open('./test.html',encoding='utf-8')
soup = BeautifulSoup(fp,'lxml')
# soup.div
# soup.find('div',class_='song')
# soup.findAll('div',class_='song')
# soup.select('#feng')[0]
# soup.select('.tang > ul > li > a')
# soup.select('.tang a')
# tag = soup.b
# tag.string
# div_tag = soup.find('div',class_='tang')
# div_tag.text
a_tag = soup.select('#feng')[0]
a_tag
- 使用bs4解析三国演义小说的标题和内容,存储到本地 :
main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
page_text = requests.get(url=main_url,headers=headers).text
#数据解析:章节的标题和详情页的url
soup = BeautifulSoup(page_text,'lxml')
a_list = soup.select('.book-mulu > ul > li > a')
fp = open('./sanguo.txt','w',encoding='utf-8')
for a in a_list:
title = a.string
detail_url = 'http://www.shicimingju.com'+a['href']
detail_page_text = requests.get(url=detail_url,headers=headers).text
#数据解析:章节内容
detail_soup = BeautifulSoup(detail_page_text,'lxml')
div_tag = detail_soup.find('div',class_='chapter_content')
content = div_tag.text
fp.write(title+':'+content+'\n')
print(title,'写入成功!!!')
fp.close()
xpath解析
-
- 环境的安装 - pip install lxml - 解析原理 - 实例化一个etree的对象,且把即将被解析的页面源码数据加载到该对象中 - 调用etree对象中的xpath方法结合这不同形式的xpath表达式进行标签定位和数据提取 - etree对象的实例化 - etree.parse('fileName') - 本地文档 - etree.HTML(page_text) - 网络请求 - 标签定位 - 最左侧的/:一定要从根标签开始进行标签定位 - 非最左侧的/:表示一个层级 - 最左侧的//:可以从任意位置进行指定标签的定位 - 非最左侧的//:表示多个层级 - 属性定位://tagName[@attrName="value"] - 索引定位://tagName[@attrName="value"]/li[2],索引是从1开始 - 逻辑运算: - 找到href属性值为空且class属性值为du的a标签 - //a[@href="" and @class="du"] - 模糊匹配: - //div[contains(@class, "ng")] - //div[starts-with(@class, "ta")] - 取文本 - /text():直系的文本内容 - //text():所有的文本内容 - 取属性 - /@attrName
from lxml import etree
tree = etree.parse('./test.html')
# tree.xpath('/html//title')
# tree.xpath('//div')
# tree.xpath('//div[@class="tang"]')
# tree.xpath('//div[@class="tang"]/ul/li[2]')
# tree.xpath('//p[1]/text()')
# tree.xpath('//div[@class="song"]//text()')
tree.xpath('//img/@src')[0]
- 需求:爬取虎牙主播名称,热度和标题
url = 'https://www.huya.com/g/xingxiu'
page_text = requests.get(url=url,headers=headers).text
#数据解析
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="box-bd"]/ul/li')
for li in li_list:
#实现的是页面局部数据的指定数据的解析
title = li.xpath('./a[2]/text()')[0]
author = li.xpath('./span/span[1]/i/text()')[0]
hot = li.xpath('./span/span[2]/i[2]/text()')[0]
print(title,author,hot)
- 爬取http://pic.netbian.com/4kmeinv/中前五页的图片数据
- 中文乱码的处理
- 多页码数据的爬取
# url = 'http://pic.netbian.com/4kmeinv/' #第一页
#指定一个通用的url模板:不可变的
url = 'http://pic.netbian.com/4kmeinv/index_%d.html'
dirName = './MZLib'
if not os.path.exists(dirName):
os.mkdir(dirName)
for page in range(1,6):
if page == 1:
new_url = 'http://pic.netbian.com/4kmeinv/'
else:
new_url = format(url%page)
page_text = requests.get(url=new_url,headers=headers).text
#数据解析:图片地址&图片名称
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]/ul/li')
for li in li_list:
img_name = li.xpath('./a/img/@alt')[0]
img_name = img_name.encode('iso-8859-1').decode('gbk')+'.jpg'
img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]
img_data = requests.get(img_src,headers=headers).content #图片的二进制类型数据
img_path = dirName+'/'+img_name
with open(img_path,'wb') as fp:
fp.write(img_data)
print('第{}页爬取完毕!!!'.format(page))
- 爬取全国城市的名称
url = 'https://www.aqistudy.cn/historydata/'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
# hot_cities = tree.xpath('//div[@class="bottom"]/ul/li/a/text()')
# all_cities = tree.xpath('//div[@class="bottom"]/ul/div[2]/li/a/text()')
tree.xpath('//div[@class="bottom"]/ul/div[2]/li/a/text() | //div[@class="bottom"]/ul/li/a/text()')
7. 代理
代理指的就是代理服务器
代理的作用 :
请求和响应数据的转发
代理和爬虫之间的关联 :
可以基于代理实现更换爬虫程序请求的ip地址
代理网站 :
1. 西祠 https://www.xicidaili.com/nn/
2. 快代理
3. www.goubanjia.comm
4. 代理精灵 http://http.zhiliandaili.cn/
代理的匿名度 :
高匿 : 所访问的服务器察觉不到是否是代理访问,也无法知晓真正访问的ip
匿名 : 所访问的服务器知道是代理访问,但无法查到真正的ip
透明 : 知道是代理,并且知道真实ip
类型 :
http
https
# 使用代理发请求
import requests
headers = {
'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',
'Connection':'close'
}
url = 'https://www.baidu.com/s?ie=UTF-8&wd=ip'
page_text = requests.get(url,headers=headers,proxies={'https':'125.87.99.237:22007'}).text
with open('./ip.html','w',encoding='utf-8') as fp:
fp.write(page_text)
- 搭建一个免费的代理池 (利用付费代理ip爬取免费代理网站的ip)
#构建一个付费的代理池
import random
ips_pool = []
url = 'http://ip.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=103&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
ip_list = tree.xpath('//body//text()')
for ip in ip_list:
dic = {'https':ip}
ips_pool.append(dic)
from lxml import etree
url = 'https://www.xicidaili.com/nn/%d' #通用的url模板(不可变)
all_ips = []
for page in range(1,5):
new_url = format(url%page)
page_text = requests.get(new_url,headers=headers,proxies=random.choice(ips_pool)).text
tree = etree.HTML(page_text)
#在xpath表达式中不可以出现tbody标签
tr_list = tree.xpath('//*[@id="ip_list"]//tr')[1:]
for tr in tr_list:
ip = tr.xpath('./td[2]/text()')[0]
port = tr.xpath('./td[3]/text()')[0]
type_ip = tr.xpath('./td[6]/text()')[0]
dic = {
'ip':ip,
'port':port,
'type':type_ip
}
all_ips.append(dic)
print(len(all_ips))
8. cookie
需求:将https://xueqiu.com/中的新闻数据进行爬取
爬虫中处理cookie的操作
手动处理:将cookie写在headers中
自动处理:session对象。
获取session对象:requests.Session()
作用:
session对象和requests对象都可以对指定的url进行请求发送。只不过使用session进行请求发送的过程中如果产生了cookie则cookie会被自动存储在session对象中
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352188&count=15&category=-1'
news_json = requests.get(url,headers=headers).json()
news_json
#基于cookie操作的修正
session = requests.Session()
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352188&count=15&category=-1'
#将cookie存储到session中,目的是将cookie获取存储到session中
session.get('https://xueqiu.com/',headers=headers)
#保证该次请求时携带对应的cookie才可以请求成功
news_json = session.get(url,headers=headers).json()
news_json
9. 验证码的识别
使用线上的打码平台进行自动的识别:
- 云打码
- 超级鹰 :
- 注册《用户中心》身份的账户
- 登陆
- 创建一个软件
- 下载示例代码《开发文档》
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: 图片字节
codetype: 题目类型 参考 http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:报错题目的图片ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') #用户中心>>软件ID 生成一个替换 96001
im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
print(chaojiying.PostPic(im,1004)['pic_str'])
#验证码识别函数的封装
def transformCode(imgPath,imgType):
chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370')
im = open(imgPath, 'rb').read()
return chaojiying.PostPic(im,imgType)['pic_str']
模拟登陆
版本一 :
版本一的问题 :
请求需要有动态的参数
通常请情况下动态变化的请求参数都会被隐藏在前台页面源码中
from urllib import request
#验证码的识别:将验证码下载到本地然后提交给打吗平台进行识别
main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = requests.get(main_url,headers=headers).text
tree = etree.HTML(page_text)
code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
request.urlretrieve(code_src,'./code.jpg')
#识别验证码
code_text = transformCode('./code.jpg',1004)
login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
'__VIEWSTATE': '8/BKAQBaZHn7+GP+Kl2Gx43fFO1NI32RMyVae0RyrtFQue3IAhzQKvkml41cIT42Y//OcQccA8AqGYkvB+NFkU43uaHqU69Y0Z1WT3ZRrr4vR+CF7JlBG29POXM=',
'__VIEWSTATEGENERATOR': 'C93BE1AE',
'from': 'http://so.gushiwen.org/user/collect.aspx',
'email': 'www.zhangbowudi@qq.com',
'pwd': 'bobo328410948',
'code': code_text,
'denglu': '登录',
}
print(code_text)
page_text = requests.post(login_url,headers=headers,data=data).text
with open('./login.html','w',encoding='utf-8') as fp:
fp.write(page_text)
版本二 :
版本二遇到的问题 :
没有携带cookie ,且这个网站的cookie在验证码的请求里
#验证码的识别:将验证码下载到本地然后提交给打吗平台进行识别
main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = requests.get(main_url,headers=headers).text
tree = etree.HTML(page_text)
code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
request.urlretrieve(code_src,'./code.jpg')
#解析出动态变化的请求参数
__VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]
#识别验证码
code_text = transformCode('./code.jpg',1004)
login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
'__VIEWSTATE': __VIEWSTATE,
'__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
'from': 'http://so.gushiwen.org/user/collect.aspx',
'email': 'www.zhangbowudi@qq.com',
'pwd': 'bobo328410948',
'code': code_text,
'denglu': '登录',
}
print(code_text)
page_text = requests.post(login_url,headers=headers,data=data).text
with open('./login.html','w',encoding='utf-8') as fp:
fp.write(page_text)
版本三 (完美版):
s = requests.Session()
#验证码的识别:将验证码下载到本地然后提交给打吗平台进行识别
main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = s.get(main_url,headers=headers).text
tree = etree.HTML(page_text)
code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
# request.urlretrieve(code_src,'./code.jpg')
code_data = s.get(code_src,headers=headers).content
with open('./code.jpg','wb') as fp:
fp.write(code_data)
#解析出动态变化的请求参数
__VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]
#识别验证码
code_text = transformCode('./code.jpg',1004)
login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
'__VIEWSTATE': __VIEWSTATE,
'__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
'from': 'http://so.gushiwen.org/user/collect.aspx',
'email': 'www.zhangbowudi@qq.com',
'pwd': 'bobo328410948',
'code': code_text,
'denglu': '登录',
}
print(code_text)
page_text = s.post(login_url,headers=headers,data=data).text
with open('./login.html','w',encoding='utf-8') as fp:
fp.write(page_text)
- 反爬机制
- robots
- UA检测
- 图片懒加载
- 代理
- cookie
- 验证码
- 动态变化的请求参数
- 动态加载的数据
10. 使用线程池提升爬取数据的效率
# 同步操作
import time
start = time.time()
def request(url):
print('正在请求',url)
time.sleep(2)
print('请求完毕:',url)
urls = [
'www.1.com',
'www.b.com',
'www.3.com'
]
for url in urls:
request(url)
print('总耗时:',time.time()-start)
# 异步操作
import time
from multiprocessing.dummy import Pool
start = time.time()
pool = Pool(3)
def request(url):
print('正在请求',url)
time.sleep(2)
print('请求完毕:',url)
urls = [
'www.1.com',
'www.b.com',
'www.3.com'
]
pool.map(request,urls)
print('总耗时:',time.time()-start)
# 爬虫+ 线程池
# server端
from flask import Flask,render_template
from time import sleep
app = Flask(__name__)
@app.route('/bobo')
def index_bobo():
sleep(2)
return render_template('ip.html')
@app.route('/jay')
def index_jay():
sleep(2)
return render_template('login.html')
app.run()
# 爬虫 + 线程池
import time
from multiprocessing.dummy import Pool
import requests
from lxml import etree
start = time.time()
urls = [
'http://localhost:5000/jay',
'http://localhost:5000/bobo'
]
def get_request(url):
page_text = requests.get(url).text
return page_text
def parse(page_text):
tree = etree.HTML(page_text)
print(tree.xpath('//div[1]//text()'))
pool = Pool(2)
page_text_list = pool.map(get_request,urls)
pool.map(parse,page_text_list)
print(len(page_text_list))
print('总耗时:',time.time()-start)
11. 单线程+多任务的异步协程
1. 解释说明
1. 特殊的函数:
- 如果一个函数的定义被async修饰后,则该函数就是一个特殊的函数。
- 协程:
- 对象。特殊函数被调用后,函数内部的实现语句不会被立即执行,然后该函数
调用会返回一个协程对象。
- 结论:协程对象==特殊的函数调用
2. 任务对象
- 其实是对协程对象的进一步封装。
- 结论:任务对象==高级的协程对象==特殊的函数调用
- 绑定回调:
- 回调函数什么时候被执行?
- 任务对象执行结束后执行回调函数
- task.add_done_callback(func)
- func必须要有一个参数,该参数表示的是该回调函数对应的任务对象
- 回调函数的参数.result():任务对象对应特殊函数内部的返回值
3. 事件循环对象
- 作用:将其内部注册的任务对象进行异步执行。
- 编码流程:
- 定义特殊函数
- 创建协程对象
- 封装任务对象
- 创建事件循环对象
- 将任务对象注册到事件循环中且开启事件循环对象
- 注意:在特殊函数内部的实现语句中不可以出现不支持异步的模块对应的代码,否则
就是终止多任务异步协程的异步效果
- 注意重点:requests模块不支持异步,在多任务的异步协程中不可以使用requests
- aiohttp
- 概念:支持异步的网络请求模块
- 编码流程:
- 写基本架构:
with aiohttp.ClientSession() as s:
with s.get(url) as response:
page_text = response.text()
return page_text
- 补充细节:
- 添加async关键字
- 每一个with前加上async
- 添加await关键字
- 加载每一步的阻塞操作前加上await
- 请求
- 获取响应数据
2. 协程对象 - 特殊的函数
import asyncio
from time import sleep
#函数的定义 async修饰函数后成为特殊的函数
async def get_request(url):
print('正在请求:',url)
sleep(1)
print('请求结束:',url)
#函数调用:返回的就是一个协程对象
c = get_request('www.1.com')
#创建3个协程对象
urls = [
'1.com','2.com','3.com'
]
coroutine_list = [] # 协程对象列表
for url in urls:
c = get_request(url)
coroutine_list.append(c)
print(coroutine_list)
# 注意,此时协程对象并不能被执行
3. 基于协程对象创建任务对象
import asyncio
from time import sleep
#函数的定义
async def get_request(url):
print('正在请求:',url)
sleep(1)
print('请求结束:',url)
#函数调用:返回的就是一个协程对象
c = get_request('www.1.com')
#创建一个任务对象:基于协程对象创建
task = asyncio.ensure_future(c)
#创建3个协程对象
urls = [
'1.com','2.com','3.com'
]
task_list = [] #存放多个任务对象的列表
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c) # 封装任务对象
task_list.append(task)
4. 事件循环对象
import asyncio
from time import sleep
#函数的定义
async def get_request(url):
print('正在请求:',url)
sleep(1)
print('请求结束:',url)
c = get_request('www.1.com')
task = asyncio.ensure_future(c)
#创建一个事件循环对象
loop = asyncio.get_event_loop()
#将任务对象注册到事件循环对象中并且开启事件循环
loop.run_until_complete(task)
5. 多任务异步协程
import asyncio
from time import sleep
import time
#函数的定义
async def get_request(url):
print('正在请求:',url)
await asyncio.sleep(3) # time模块不支持异步,用asyncio代替 await为等待相关阻塞操作执行再执行
print('请求结束:',url)
#创建3个协程对象
urls = [
'1.com','2.com','3.com'
]
start = time.time()
#任务列表:存储的是多个任务对象
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) # wait为挂起
print('总耗时:',time.time()-start)
6. 给任务对象绑定回调
import asyncio
from time import sleep
import time
#函数的定义
async def get_request(url):
print('正在请求:',url)
await asyncio.sleep(3)
print('请求结束:',url)
return 'bobo'
def parse(task): # 回调函数必须要有一个参数,task为绑定的任务对象,task.result()返回的是特殊函数的返回值
print('i am task callback()!!!=----',task.result())
#创建3个协程对象
urls = [
'1.com','2.com','3.com'
]
start = time.time()
#任务列表:存储的是多个任务对象
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
#绑定回调函数
task.add_done_callback(parse)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print('总耗时:',time.time()-start)
7. 多任务异爬虫
爬取自己搭建的服务器
服务端 :
from flask import Flask,render_template
from time import sleep
app = Flask(__name__)
@app.route('/ip')
def index_1():
sleep(2)
return render_template('ip.html')
@app.route('/ip1')
def index_2():
sleep(2)
return render_template('ip.html')
app.run(debug=True)
爬取
import asyncio
import requests
import time
import aiohttp
from lxml import etree
#特殊函数:发起请求获取页面源码数据
# async def get_request(url):
# #requests是一个不支持异步的模块
# page_text = requests.get(url).text
# return page_text
async def get_request(url):
async with aiohttp.ClientSession() as s:
#get/post:proxy = 'http://ip:port'
#url,headers,data/prames跟requests一直
async with await s.get(url) as response:
page_text = await response.text()#text()字符串形式的响应数据。read()二进制的响应数据
return page_text
def parse(task):
page_text = task.result()
tree = etree.HTML(page_text)
print(tree.xpath('//*[@id="10"]//text()'))
urls = [
'http://localhost:5000/ip1',
'http://localhost:5000/ip',
'http://localhost:5000/ip1',
'http://localhost:5000/ip',
'http://localhost:5000/ip1',
'http://localhost:5000/ip',
]
start = time.time()
tasks = [] #任务列表
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
#绑定回调:用作于数据解析
task.add_done_callback(parse)
tasks.append(task)
loop = asyncio.get_event_loop() # 创建事物循环对象
loop.run_until_complete(asyncio.wait(tasks)) # 将任务对象注册到事件循环对象中并且开启事件循环
print('总耗时:',time.time()-start)
12. selenium
selenium :
- 概念:基于浏览器自动化的一个模块。
- Appium是基于手机的自动化的模块。
- selenium和爬虫之间的关联
- 便捷的爬取到动态加载的数据
- 可见即可得
- 便捷的实现模拟登陆
- 基本使用:
- 环境安装
- pip install selenium
- 下载浏览器的驱动程序
- http://chromedriver.storage.googleapis.com/index.html
- 浏览器版本和驱动程序的映射关系: https://blog.csdn.net/huilan_same/article/details/51896672
- 动作链
- 在使用find系列的函数进行标签定位的时候如果出现了NoSuchElementException如何处理?
- 如果定位的标签是存在于iframe标签之下的,则在进行指定标签定位的时候
必须使用switch_to.frame()的操作才可。
无头浏览器 :
- phantomjs
- 谷歌无头浏览器(推荐)
如何规避selenium被监测到的风险
- 网站可以根据:window.navigator.webdriver的返回值鉴定是否使用了selenium
(浏览器开发者工具的console窗口下测试)
- undefind:正常
- true:selenium 则检测到了是selenium访问的
selenium的基本使用
from selenium import webdriver
import time
#实例化某一款浏览器对象
bro = webdriver.Chrome(executable_path='chromedriver.exe')
#基于浏览器发起请求
bro.get('https://www.jd.com/')
#商品搜索
#标签定位
search_input = bro.find_element_by_id('key')
#往定位到的标签中录入数据
search_input.send_keys('袜子')
#点击搜索按钮
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')
time.sleep(2)
btn.click()
time.sleep(2)
#滚轮滑动(js注入)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
# time.sleep(6)
bro.quit() # 退出浏览器
捕获动态加载的数据
from selenium import webdriver
import time
from lxml import etree
#实例化某一款浏览器对象
bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.fjggfw.gov.cn/Website/JYXXNew.aspx')
time.sleep(1)
#page_source:当前页面所有的页面源码数据
page_text = bro.page_source
#存储前3页对应的页面源码数据
all_page_text = [page_text]
for i in range(3):
next_page_btn = bro.find_element_by_xpath('//*[@id="kkpager"]/div[1]/span[1]/a[7]')
next_page_btn.click()
time.sleep(1)
all_page_text.append(bro.page_source)
for page_text in all_page_text:
tree = etree.HTML(page_text)
title = tree.xpath('//*[@id="list"]/div[1]/div/h4/a/text()')[0]
print(title)
动作链的使用
from selenium import webdriver
from selenium.webdriver import ActionChains #动作连的类
from time import sleep
bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
sleep(1)
# - 在使用find系列的函数进行标签定位的时候如果出现了NoSuchElementException如何处理?
# - 如果定位的标签是存在于iframe标签之下的,则在进行指定标签定位的时候必须使用switch_to.frame()的操作才可。
bro.switch_to.frame('iframeResult') #frame的参数为iframe标签的id属性值
div_tag = bro.find_element_by_id('draggable')
#基于动作连实现滑动操作
action = ActionChains(bro)
#点击且长按
action.click_and_hold(div_tag)
for i in range(5):
#perform()表示让动作连立即执行
action.move_by_offset(20,0).perform()
sleep(0.5)
sleep(3)
bro.quit()
无头浏览器 - 无可视化界面
from selenium import webdriver
from time import sleep
from selenium.webdriver.chrome.options import Options
# 创建一个参数对象,用来控制chrome以无界面模式打开
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
bro = webdriver.Chrome(executable_path='chromedriver.exe',chrome_options=chrome_options)
bro.get('https://www.taobao.com/')
bro.save_screenshot('./123.png') # 截图保存 图片格式必须是png,否则报错
print(bro.page_source)
规避网站对selenium的监测
from selenium import webdriver
from time import sleep
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
# 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
bro = webdriver.Chrome(r'chromedriver.exe',options=option)
bro.get('https://www.taobao.com/')
13. 12306模拟登陆
# 超级鹰示例代码
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: 图片字节
codetype: 题目类型 参考 http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:报错题目的图片ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
from ChaoJiYing import Chaojiying_Client #导入超级鹰示例代码
from selenium import webdriver
from selenium.webdriver import ActionChains
from time import sleep
#下载pil或者是Pillow
from PIL import Image
def transformCode(imgPath,imgType): # 超级鹰封装的函数 返回的是一组坐标
chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370')
im = open(imgPath, 'rb').read()
return chaojiying.PostPic(im,imgType)['pic_str']
bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://kyfw.12306.cn/otn/login/init')
sleep(2)
bro.save_screenshot('main.png')
#在main.jpg中截取下验证码图片
img_tag = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
location = img_tag.location # 图片左上角坐标
size = img_tag.size #img标签对应图片的长宽(尺寸)
#裁剪范围
rangle = (location['x'],location['y'],location['x']+size['width'],location['y']+size['height'])
# 裁剪12306验证码
i = Image.open('./main.png')
frame = i.crop(rangle)
frame.save('code.png')
result = transformCode('./code.png',9004)
# 识别后的坐标原点是基于识别图片而来的
# 坐标转换 260,140|260,139 ==> [[260,140],[260,139]]
all_list = []#[[260,140],[260,139]] # 坐标列表
if '|' in result:
list_1 = result.split('|')
count_1 = len(list_1)
for i in range(count_1):
xy_list = []
x = int(list_1[i].split(',')[0])
y = int(list_1[i].split(',')[1])
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
else:
x = int(result.split(',')[0])
y = int(result.split(',')[1])
xy_list = []
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
for xy in all_list:
x = xy[0]
y = xy[1]
# 要先找到识别图片的标签img_tag,然后基于识别图片坐标进行点击事件
ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform()
sleep(1)
14. 空气质量数据爬取
爬虫分析
1. 在页面中更换查找条件可以让抓包工具捕获到我们想要的数据包
2. apistudyapi.php该数据包就是我们最终定位到的爬取数据对应的数据包
a. 该数据包中可以提取到url和请求参数(可能是一组密文,然后该是动态变化)
b. 响应数据的是经过加密的密文
3. 当修改了查询条件后且点击了查询按钮后发起了一个ajax请求,该请求就可以请求到apistudyapi.php数据包
a. 想要捕获的数据是可以通过点击搜索按钮生成的
4. 通过火狐浏览器的开发者工具可以找到搜索按钮绑定的点击事件对应的事件函数(getData())
5. 分析getData():在该函数实现内部没有找到ajax请求对应的操作
a. type这个变量可以为HOUR
b. getAQIData();getWeatherData();
6. 分析:getAQIData();getWeatherData();
+ 发现这两个函数的实现除了method变量的赋值不同剩下的都一致
a. method = (GETDETAIL 或者 GETCITYWEATHER)
b. 在这两个函数的实现中也没有发现ajax请求对应的代码,但是发现了一个叫做getServerData的函数调用,则分析a jax请求对应的代码肯定是存在于getServerData这个函数的实现中
c. getServerData(method, param,匿名函数,0.5)
-method = (GETDETAIL 或者 GETCITYWEATHER)
-param是一个字典,内部有四组(city,type,startTime,endTime)键值对
7.分析getServerData函数的实现 :
a. 最终通过抓包工具的全局搜索定位到了该函数的实现,但是实现的js代码被加密了,该种形式的加密被称为js混淆。
b.如何破解js混淆?
- http://www.bm8.com.cn/jsConfusion/进行js反混淆
c. 在该函数的实现中终于找到了ajax请求对应的代码:
- ajax请求的url
- ajax请求方式
- 请求参数的来源:getParam(method, param)
- 对加密的响应数据解密:decodeData(密文)
8.基于python模拟执行js代码 :
- PyExecJS模块可以让python模拟执行js代码
- 环境安装:
- pip install PyExecJS
- 在本机安装nodejs的开发环境
1. 获取ajax请求的动态变化且加密的请求参数(d:xxx)
此过程是本地化的
#获取ajax请求的动态变化且加密的请求参数(d:xxx)
import execjs
node = execjs.get() # 实例化一个对象
# Params
method = 'GETDETAIL'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'test.js' # 将ajax请求的js代码放在这里
ctx = node.compile(open(file,encoding='utf-8').read()) # 编译js代码
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js) #执行js代码
print(params) # 获取到的params是动态加密过的请求参数
2. 携带捕获到请求参数进行请求 :
#发起post请求
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params},headers=headers).text
print(response_text) # 获取到的响应数据是加密的
3. 对捕获到的加密的响应数据进行解密
#对加密的响应数据进行解密
js = 'decodeData("{0}")'.format(response_text)
decrypted_data = ctx.eval(js)
print(decrypted_data)
4. 完整代码
#对捕获到的加密的响应数据进行解密
import execjs
import requests
node = execjs.get()
# Params
method = 'GETDETAIL'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'test.js'
ctx = node.compile(open(file,encoding='utf-8').read())
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js) #执行js代码
#发起post请求
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params},headers=headers).text
#对加密的响应数据进行解密
js = 'decodeData("{0}")'.format(response_text)
decrypted_data = ctx.eval(js)
print(decrypted_data)
15. scrapy爬虫框架
1. scrapy框架
- scrapy框架
- 高性能的网络请求
- 高性能的数据解析
- 高性能的持久化存储
- 深度爬取
- 全栈爬取
- 分布式
- 中间件
- 请求传参
2. 环境的安装
- 环境的安装:
- mac/linux:pip install scrapy
- window:
- pip install wheel - 安装wheel工具
- 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted - 下载twisted架构
- 进入下载目录,执行 pip install Twisted‑17.1.0‑cp35‑cp36m‑win_amd64.whl -安装twisted 报错换版本
scrapy的异步是基于twisted实现的
- pip install pywin32
- pip install scrapy
3. 基本使用
- 基本使用
1. 新建一个工程 :pycharm终端中 :scrapy startproject ProName
目录结构:
- spiders(包):空包
- settings:配置文件
- 不遵从robots
- UA伪装
- 日志等级的指定
2. 命令行进入工程目录中 : cd ProName3
3. 在spiders(爬虫文件夹(包))中创建一个py爬虫文件 : scrapy genspider pachong www.xxx.com
该pachong文件创建在了spiders文件夹中
4. 编写代码 : 主要的爬虫代码写在了爬虫文件中
5. 执行工程 : 命令行中 : scrapy crawl pachong
scrapy crawl pachong --nolog 不显示日志
4. 爬虫文件说明
# -*- coding: utf-8 -*-
import scrapy
# 爬虫类 父类是 Spider
class PachongSpider(scrapy.Spider):
name = 'pachong' # name 爬虫文件名 : 当前爬虫源文件的唯一标识
# 被允许的域名
allowed_domains = ['www.xxx.com']
# 起始的url,列表中存放的url都可以被scrapy进行异步网络请求
start_urls = ['http://www.xxx.com/']
# parse用作数据解析,response为响应对象
def parse(self, response):
pass
5. settings的一些配置
1. 指定打印日等级 LOG_LEVEL = 'ERROR'
2. 不遵从robots协议 ROBOTSTXT_OBEY = False
3. UA伪装 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
6. 数据解析+持久化存储
- scrapy的数据解析
- extract、extract_first()作用
一般流程 :
1. 新建工程
- 终端中 :
1. scrapy startproject scrapy01
2. cd scrapy01
2. scrapy genspider pachong www.xx.com
2. settings配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
3. 写爬虫代码
# -*- coding: utf-8 -*-
import scrapy
class PachongSpider(scrapy.Spider):
name = 'pachong'
# allowed_domains = ['www.xx.com']
start_urls = ['https://dig.chouti.com']
def parse(self, response):
# 解析数据
div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div')
for div in div_list:
content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract()
content1 = div.xpath('.//a[@class="link-title link-statistics"]/text()').extract()[0]
author = div.xpath('.//span[@class="left author-name"]/text()').extract_first()
print(content,content1,author)
# 测试 ,只取一个
break
4. 执行爬虫代码
终端 : scrapy crawl pachong
持久化存储
一、基于终端指令进行的持久化存储
- 基于终端指令进行持久化存储
- 只可以将parse方法的返回值存储到本地的磁盘文件(指定形式后缀)中
- scrapy crawl spiderName -o filePath
-
爬虫代码
# -*- coding: utf-8 -*- import scrapy class PachongSpider(scrapy.Spider): name = 'pachong' # allowed_domains = ['www.xx.com'] start_urls = ['https://dig.chouti.com'] def parse(self, response): all_data = [] # 解析数据 div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div') for div in div_list: content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract() content1 = div.xpath('.//a[@class="link-title link-statistics"]/text()').extract()[0] author = div.xpath('.//span[@class="left author-name"]/text()').extract_first() dic = { 'content':content, 'auther':author } all_data.append(dic) return all_data
-
持久化存储
只支持后缀 'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle' 终端命令 : scrapy crawl pachong -o data.csv
二、基于管道进行持久化存储(重点)
- 基于管道进行持久化存储(重点)
- 编码流程
- 1.在爬虫文件中进行数据解析
- 2.在item类中定义相关的属性
- 3.将解析到的数据存储到一个item类型的对象中
- 4.将item类型的对象提交给管道
- 5.管道类的process_item方法负责接受item,接受到后可以对item实现任意形式的持久化存储操作
- 6.在配置文件中开启管道
- 一个管道类对应一种平台的持久化存储
-
爬虫代码 - 在爬虫文件中进行数据解析
# -*- coding: utf-8 -*- import scrapy # 导入Item的类,后期实例化需要 from scrapy002.items import Scrapy002Item class PachongSpider(scrapy.Spider): name = 'pachong' # allowed_domains = ['www.xx.com'] start_urls = ['https://dig.chouti.com'] def parse(self, response): all_data = [] # 解析数据 div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div') for div in div_list: # xpath在取标签的时候必须要用exetract()、exetract_first()进行字符串提取 content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract() author = div.xpath('.//span[@class="left author-name"]/text()').extract_first() # 实例化一个item类型的对象 item = Scrapy002Item() # 给item对象的属性赋值 item['content'] = content item['author'] = author yield item # 将item提交给管道
-
items.py - 在item类中定义相关的属性
import scrapy class Scrapy002Item(scrapy.Item): # Field 类型是一个万能的数据类型 author = scrapy.Field() content = scrapy.Field()
-
pipelines.py - 将item类型的对象提交给管道 - 本地化存储
爬取的数据存储到本地 :
class Scrapy002Pipeline(object): # 重写父类,且该方法只会执行一次 def open_spider(self,spider): print('爬虫开始 ...') self.fp = open('pachong_ingf.txt','w',encoding='utf-8') # 该方法调用后就可接收爬虫类提交的item对象,且赋值给item参数 def process_item(self, item, spider): author = item['author'] content = item['content'] self.fp.write(author+':'+content+'\n') return item def close_spider(self,spider): print('爬虫结束!') self.fp.close()
爬取的数据存储到数据库中 :
1.在pipelines.py中写存储到数据库的类
class Scrapy002Pipeline(object): # 重写父类,且该方法只会执行一次 def open_spider(self,spider): print('爬虫开始 ...') self.fp = open('pachong_ingf.txt','w',encoding='utf-8') # 该方法调用后就可接收爬虫类提交的item对象,且赋值给item参数 def process_item(self, item, spider): author = item['author'] content = item['content'] self.fp.write(author+':'+content+'\n') return item # 此处是讲item传递给下一个即将执行的管道类 def close_spider(self,spider): print('爬虫结束!') self.fp.close() import pymysql class Mysql_data(object): conn = None cursor = None def open_spider(self, spider): self.conn = pymysql.Connection(host='127.0.0.1',port = 3306,user = 'root',password='2108',db = 'pachong',charset='utf8') def process_item(self, item, spider): author = item['author'] content = item['content'] sql = 'insert into pachong values ("%s","%s")'%(author,content) self.cursor = self.conn.cursor() #事物处理 : try : self.cursor.execute(sql) self.conn.commit() except Exception as e : print(e) self.conn.rollback() # 回滚事物 return item def close_spider(self,spider): self.cursor.close() self.conn.close()
-
在配置中注册相应的管道
ITEM_PIPELINES = { # 'scrapy002.pipelines.Scrapy002Pipeline': 300, 'scrapy002.pipelines.Mysql_data': 301, }
-
-
settings配置
开启管道,300为优先级,数值越小.优先级越高 ITEM_PIPELINES = { 'scrapy002.pipelines.Scrapy002Pipeline': 300, }
16. scrapy的图片数据爬取(数据流的爬取)
- scrapy的图片数据爬取(流数据的爬取)
- scrapy中封装好了一个管道类(ImagesPipeline),基于该管道类可以实现图片资源的请求和持久化存储
- 编码流程:
- 爬虫文件中解析出图片的地址
- 将图片地址封装到item中且提交给管道
- 管道文件中自定义一个管道类(父类:ImagesPipeline)
- 重写三个方法:
- def get_media_requests(self,item,info):
- def file_path(self,request,response=None,info=None):
- def item_completed(self,result,item,info):
- 在配置文件中开启管道且加上IMAGES_STORE = './imgLibs'
-
写爬虫代码
import scrapy from imgage_pa.items import ImgagePaItem class ImgSpider(scrapy.Spider): name = 'img' # allowed_domains = ['www.xx.com'] start_urls = ['http://www.521609.com/xiaoyuanmeinv/'] def parse(self, response): li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: item = ImgagePaItem() img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first() item['img_src'] = img_src yield item
-
items.py - 在item类中定义相关的属性
import scrapy class ImgagePaItem(scrapy.Item): img_src = scrapy.Field()
-
pipelines.py - 将item类型的对象提交给管道
from scrapy.pipelines.images import ImagesPipeline import scrapy class ImgagePaPipeline(ImagesPipeline): # 该方法是用于请求发送的 def get_media_requests(self, item, info): print(item) yield scrapy.Request(url=item['img_src']) # 指定图片路径 (文件夹 + 文件名) def file_path(self, request, response=None, info=None): return request.url.split('/')[-1] # 将item传递给下一个即将执行的管道类 def item_completed(self, results, item, info): return item
-
settings配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR' IMAGES_STORE = './images' #指定爬取的图片存放的路径 ITEM_PIPELINES = { 'imgage_pa.pipelines.ImgagePaPipeline': 300, }
17. 在scrapy中如何进行手动请求发送
实现多页数据爬取
# 爬虫代码py文件中
# -*- coding: utf-8 -*-
import scrapy
from imgage_pa.items import ImgagePaItem
class ImgSpider(scrapy.Spider):
name = 'img'
# allowed_domains = ['www.xx.com']
start_urls = ['http://www.521609.com/xiaoyuanmeinv/']
# 定义个通用的url模板
url = 'http://www.521609.com/xiaoyuanmeinv/list_%d.html'
page_Num = 1
def parse(self, response):
li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
for li in li_list:
item = ImgagePaItem()
img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first()
item['img_src'] = img_src
yield item
if self.page_Num < 5: # 递归结束条件 - 爬取页码数
self.page_Num += 1
new_url = format((self.url % self.page_Num))
yield scrapy.Request(new_url, callback=self.parse) # 递归调用parse
在scrapy中如何进行手动请求发送
在scrapy中如何进行post请求的发送?
如何对起始的url进行post请求的发送?
- 在scrapy中如何进行手动请求发送
- yield scrapy.Request(url,callback)
- 在scrapy中如何进行post请求的发送?
- yield scrapy.FormRequest(url,callback,formdata)
- 如何对起始的url进行post请求的发送?
- 重写父类的start_requests(self):
def start_requests(self):
for url in self.start_urls:
yield scrapy.FormRequest(url,callback=self.parse,formdata={}) # 调用parse函数
18. 在scrapy中如何提升爬取数据的效率?
1. 增加并发:
默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
2. 降低日志级别:
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘ERROR’
3. 禁止cookie:
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
4. 禁止重试:
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
5. 减少下载超时:
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 1 超时时间为10s
19. 请求传参 + 核心组件
- 请求传参(深度爬取)
- 深度爬取:
- 爬取的数据没有存在同一张页面中。
- 如何实现请求传参
- Request(url,callback,meta={}):可以将meta字典传递给callback
- callback接收item:response.meta
请求传参
爬虫代码
# -*- coding: utf-8 -*-
import scrapy
from movie.items import MovieItem
class MoviePaSpider(scrapy.Spider):
name = 'movie_pa'
# allowed_domains = ['www.xx.com']
start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E7%A7%91%E5%B9%BB/id/8.html ']
# 通用URL模板
url = 'https://www.4567tv.tv/index.php/vod/show/class/科幻/id/8/page/%d.html'
pageNum = 2
# 解析电影名称和详情页的url
def parse(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/div/h4/a/text()').extract_first()
detail_url ='https://www.4567tv.tv'+ li.xpath('./div/div/h4/a/@href').extract_first()
# 实例化item
item = MovieItem()
item['title'] = title
# 手动请求发送
yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
# 爬取多页
if self.pageNum < 5:
new_url = format(self.url%self.pageNum)
self.pageNum += 1
yield scrapy.Request(new_url,callback = self.parse)
# 解析详情页中的电影简介
def parse_detail(self,response):
item = response.meta['item']
detail_content = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
item['detail_content'] = detail_content
yield item
items
import scrapy
class MovieItem(scrapy.Item):
title = scrapy.Field()
detail_content = scrapy.Field()
管道pipelines.py
class MoviePipeline(object):
def process_item(self, item, spider):
print(item)
return item
settings配置
省略
scrapy的五大核心组件
1. 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
2. 调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址,放在队列中
3. 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
4. 爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
5. 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
20. scrapy的中间件
1. 有哪些中间件。
- 下载中间件(推荐)
- 爬虫中间件
2. 下载中间件的作用
- 批量拦截所有的请求和响应
3. 为什么拦截请求
- 篡改请求的头信息(UA)
- request.headers['User-Agent'] = 'xxxxx'
- 代理
- request.meta['proxy'] = 'http://ip:port'
4. 为什么拦截响应
- 篡改响应数据
- 篡改响应对象(推荐)
中间件的使用
-
写爬虫代码
import scrapy class MiddleSpider(scrapy.Spider): name = 'middle___' # allowed_domains = ['www.xx.com'] start_urls = ['http://www.baidu.com/'] def parse(self, response): pass
-
写中间件
from scrapy import signals import random # UA池 user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "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 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ] # 代理池 PROXY_http = [ 'http://153.180.102.104:80', 'http://195.208.131.189:56055', ] PROXY_https = [ 'https://120.83.49.90:9000', 'https://95.189.112.214:35508', ] class NewsWangyiDownloaderMiddleware(object): # 拦截请求与 def process_request(self, request, spider): # 可以将拦截到的请求尽可能多的设置随机的UA request.headers['User-Agent'] = random.choice(user_agent_list) # 代理ip的设置 # if request.url.split(':')[0] == 'http': # request.meta['proxy'] = random.choice(PROXY_http) # else: # request.meta['proxy'] = random.choice(PROXY_https) return None # 拦截所有的响应 def process_response(self, request, response, spider): print('拦截响应') return response # 拦截异常的请求 def process_exception(self, request, exception, spider): print('异常拦截') # 将修正后的请求对象进行发送 return request # 将异常的请求重新发送 但是不会循环发送,发送几个后还是异常就不再进行发送
-
settings配置中开启中间件
DOWNLOADER_MIDDLEWARES = { 'news_wangyi.middlewares.NewsWangyiDownloaderMiddleware': 543, }
网易新闻的爬取
网易新闻(国内,国际,军事,航空,无人机)新闻数据的标题和内容 :
分析如下:
1.每个板块下对应的新闻数据都是动态加载的
2.会对五个板块的响应数据进行数据解析,但是板块对应的响应对象是不包含动态加载的新闻数据,目前
获取的每一个板块对应的响应对象是不满足需求的响应对象!!!
3.将不满足需求的5个响应对象(工程中一共会有1+5+n),修改成满足需求。
- 找到指定的5个不满足需求的响应对象(中间件)
- 你的redis如果不可以写入字典
- pip install -U redis==2.10.6
爬虫过程
1. 新建工程
建工程 : scrapy startproject wangyiNews
建爬虫文件 :
cd wangyiNews
scrapy genspider wangyipachong
2. 写爬虫代码
import scrapy
from selenium import webdriver
from wangyiNews.items import WangyinewsItem
class WangyiPaSpider(scrapy.Spider):
name = 'wangyi_pa'
# allowed_domains = ['www.xx.com']
start_urls = ['https://news.163.com/']
module_urls = [] # 五个板块所对应的url
bro = webdriver.Chrome(executable_path=r'E:\Coding_Peasant\Project\爬虫\chromedriver.exe')
# 解析出每一个板块所对应的url
def parse(self, response):
li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
# 获取 国内,国际,军事,航空,无人机板块的url
indexs = [3, 4, 6, 7, 8]
for index in indexs:
li_tag = li_list[index]
# 解析到每一个板块对应的url
module_url = li_tag.xpath('./a/@href').extract_first()
# 将解析到的每一个板块的url放到module_url属性中,传递给中间件
self.module_urls.append(module_url)
# 对板块的url进行请求发送获取到每一个板块对应的页面
# 手动发送请求
yield scrapy.Request(module_url, callback=self.parse_each_page)
# 自定义解析每个板块下对应的每一个页面的新闻标题
def parse_each_page(self, response):
div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
for div in div_list:
item = WangyinewsItem()
detail_url = div.xpath('./a/@href').extract_first()
title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
if title and detail_url:
item['title']=title
# 交给parse_detail解析新闻详情
yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
# 解析新闻内容
def parse_detail(self,response):
item = response.meta['item']
# //text() 返回的是列表,所以要用extract()
content = response.xpath('//*[@id="endText"]//text()').extract()
# 将新闻内容content列表转换成
content = ''.join(content)
item['content'] = content
yield item
# 该方法在最后执行,关闭浏览器
def closed(self,spider):
self.bro.quit()
3. 中间件拦截响应
from scrapy import signals
from scrapy.http import HtmlResponse
from time import sleep
class WangyinewsDownloaderMiddleware(object):
def process_request(self, request, spider):
return None
# 可以拦截到1+5+n个响应对象
def process_response(self, request, response, spider):
# 根据5个板块的url定位到指定的request
# 根据request定位到指定的response
# 实例化selenium
bro = spider.bro
# spider是爬虫类实例化好的对象
module_urls = spider.module_urls
if request.url in module_urls:
# response就是最终五大板块对应的响应对象
bro.get(request.url)
sleep(1)
# 滚轮滚动
# bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
# sleep(1)
# bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
# sleep(1)
# bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
# sleep(1)
page_text = bro.page_source
# url是响应请求所对应的url, body是获取到的响应数据
new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
return new_response
else:
return response
def process_exception(self, request, exception, spider):
pass
4. items
import scrapy
class WangyinewsItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
5.管道pipelines
import pymysql
from redis import Redis
# 将爬取到的数据存储在sql中
class WangyinewsPipeline(object):
conn = None
cursor = None
def open_spider(self,spider):
self.conn = pymysql.Connection(host='127.0.0.1',port=3306,user='root',password='2108',db='pachongend',charset='utf8')
print(self.conn)
def process_item(self, item, spider):
sql = 'insert into news values ("%s","%s")'%(item['title'],item['content'])
self.cursor = self.conn.cursor()
try:
self.cursor.execute(sql)
self.conn.commit()
except Exception as e :
print(e)
self.conn.rollback()
return item
def close_spider(self,spider):
self.cursor.close()
self.conn.close()
# 将爬取到的数据存储在redis中
class Wangyi_redis(object):
conn = None
def open_spider(self,spider):
self.conn = Redis(host='127.0.0.1',port=6379)
print(self.conn)
def process_item(self,item,spider):
self.conn.lpush('news',item['title'])
self.conn.lpush('content',item['content'])
6. settings配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
# 开启管道
ITEM_PIPELINES = {
# 'wangyiNews.pipelines.WangyinewsPipeline': 300,
'wangyiNews.pipelines.Wangyi_redis': 300,
}
基于百度AI的自然语言处理
http://ai.baidu.com/
自然语言处理 → 创建应用 → 进入文档 → 自然语言处理 → 语言处理基础技术 → SDK文档 → Python语言
1. 安装自然语言处理Python SDK
pip install baidu-aip
示例代码 :
""" 你的 APPID AK SK """
APP_ID = '17533126'
API_KEY = '7W0ed4MYLHzlfke8ZoM1FwRr'
SECRET_KEY = '0Gq28MBrwV4Yipyt07cxC9ON0xRichRb'
client = AipNlp(APP_ID, API_KEY, SECRET_KEY)
title = "欧洲冠军杯足球赛"
content = "欧洲冠军联赛是欧洲足球协会联盟主办的年度足球比赛,代表欧洲俱乐部足球最高荣誉和水平,被认为是全世界最高素质、最具影响力以及最高水平的俱乐部赛事,亦是世界上奖金最高的足球赛事和体育赛事之一。"
""" 调用文章标签 """
dic = client.topic(title, content)
print(dic)
文章标签和文章分类处理 :
将百度的aip作用到管道上即可
import pymysql
from redis import Redis
from aip import AipNlp
# 将爬取到的数据存储在sql中
class WangyinewsPipeline(object):
conn = None
cursor = None
def open_spider(self,spider):
self.conn = pymysql.Connection(host='127.0.0.1',port=3306,user='root',password='2108',db='pachongend',charset='utf8')
print(self.conn)
def process_item(self, item, spider):
sql = 'insert into news values ("%s","%s")'%(item['title'],item['content'])
self.cursor = self.conn.cursor()
try:
self.cursor.execute(sql)
self.conn.commit()
except Exception as e :
print(e)
self.conn.rollback()
return item
def close_spider(self,spider):
self.cursor.close()
self.conn.close()
# 将爬取到的数据存储在redis中
class Wangyi_redis(object):
conn = None
client = None
def open_spider(self,spider):
""" 你的 APPID AK SK """
APP_ID = '17533126'
API_KEY = '7W0ed4MYLHzlfke8ZoM1FwRr'
SECRET_KEY = '0Gq28MBrwV4Yipyt07cxC9ON0xRichRb'
self.client = AipNlp(APP_ID, API_KEY, SECRET_KEY)
def process_item(self,item,spider):
title = item['title']
content = item['content']
content = content.replace(u'\xa0',u'') # 解决gbk对'\xa0'编码的问题
n_tag = self.client.keyword(title, content)['items'][0]['tag']
n_type = self.client.topic(title, content)['item']['lv2_tag_list'][0]['tag']
print(n_tag,n_type)
21. CrawlSpider实现的全站数据的爬取
CrawlSpider实现的全栈数据的爬取
1. 新建工程 srcapy startproject ProName
2. cd 工程
3. 创建爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com
配置settings
4. 连接提取器LinkExtractor
可以根据指定的规则对指定的连接进行提取【提取的规则就是构造方法中的allow(‘正则表达式’)参数决定】
5. 规则解析器Rule
可以将将连接提取器提取到的连接进行请求发送,可以根据指定的规则(callback)对请求到的数据进行解析
6. follow=True
将连接提取器 继续作用到 连接提取器提取到的连接 所对应的 页面源码中
7. 执行爬虫命令 : scrapy ceawl py文件名
请求发送的三种方式:
1. 起始url
2. scrapy.Request()
3. 链接提取器发送请求提取器
一般爬虫:
-
settings配置
-
写爬虫代码
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xx.com'] # 起始url不做解析 start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] # link 链接提取器:根据指定的规则对指定的链接进行提取链接 # 提取的规则就是构造方法中的allow('正则表达式')参数决定 # LinkExtractor可以对页面的所有url进行提取allow=r''表示的页面的所有url, link = LinkExtractor(allow=r'type=4&page=\d+') rules = ( # 实例化一个rule对象(规则解析对象) # 对提取到的链接自动发送请求,根据指定的规则callback='parse_item'对请求的对象解析 # follow=True 表示的是将提取到的链接继续作用到 链接提取器上再次进行新提取的链接页面的链接提取 Rule(link, callback='parse_item', follow=False ), ) # 数据解析:解析提取链接请求所返回的响应 def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() print(title,status)
基于CrawlSpider的深度爬取
1. settings配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
# 开启管道
ITEM_PIPELINES = {
'sun_pa.pipelines.SunPaPipeline': 300,
}
2. 爬虫代码
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sun_pa.items import SunPaItem1,SunPaItem2
class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xx.com']
# 起始url不做解析
start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
# link 链接提取器:根据指定的规则对指定的链接进行提取链接
# 提取的规则就是构造方法中的allow('正则表达式')参数决定
# LinkExtractor可以对页面的所有url进行提取allow=r''表示的页面的所有url,
link = LinkExtractor(allow=r'type=4&page=\d+')
link1 = LinkExtractor(allow=r'type=4&page=$') # 第一页
link_detail = LinkExtractor(allow=r'/question/\d+/\d+\.shtml') # \.转义点
rules = (
# 实例化一个rule对象(规则解析对象)
# 对提取到的链接自动发送请求,根据指定的规则callback='parse_item'对请求的对象解析
# follow=True 表示的是将提取到的链接继续作用到 链接提取器上再次进行新提取的链接页面的链接提取
Rule(link, callback='parse_item', follow=False ),
Rule(link1, callback='parse_item', follow=False ),
Rule(link_detail, callback='parse_detail', follow=False ),
)
# 数据解析:解析提取链接请求所返回的响应
def parse_item(self, response):
tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
for tr in tr_list:
title = tr.xpath('./td[2]/a[2]/text()').extract_first()
status = tr.xpath('./td[3]/span/text()').extract_first()
num = tr.xpath('./td[1]/text()').extract_first()
item = SunPaItem2()
item['title']=title
item['status']=status
item['num']=num
yield item
# 解析详情页中的新闻内容
def parse_detail(self, response):
content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td//text()').extract()
content = ''.join(content)
num = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first()
num = num.split(':')[-1]
item = SunPaItem1()
item['content']= content
item['num']= num
yield item
3. items
import scrapy
class SunPaItem1(scrapy.Item):
content = scrapy.Field()
num = scrapy.Field()
class SunPaItem2(scrapy.Item):
title = scrapy.Field()
status = scrapy.Field()
num = scrapy.Field()
4. 持久化存储
class SunPaPipeline(object):
def process_item(self, item, spider):
if item.__class__.__name__ == 'SunPaItem1':
content = item['content']
num = item['num']
print(num,content)
else:
title = item['title']
status = item['status']
num = item['num']
print(num,status,title)
# 最后根据相应的num进行持续化存储
return item
这个网站的前5页和最后页的标签格式不同,所以上述的基于CrawlSpider的深度爬取等我基于正则匹配url不能解析到最后页的url,爬取不到数据,下面更改用手动请求进行爬取
下面只附上爬虫代码
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sun_pa.items import SunPaItem2
class SunSpider(CrawlSpider):
name = 'sun'
# allowed_domains = ['www.xx.com']
# 起始url不做解析
start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
# link 链接提取器:根据指定的规则对指定的链接进行提取链接
# 提取的规则就是构造方法中的allow('正则表达式')参数决定
# LinkExtractor可以对页面的所有url进行提取allow=r''表示的页面的所有url,
link = LinkExtractor(allow=r'type=4&page=\d+')
link1 = LinkExtractor(allow=r'type=4&page=$') # 第一页
rules = (
# 实例化一个rule对象(规则解析对象)
# 对提取到的链接自动发送请求,根据指定的规则callback='parse_item'对请求的对象解析
# follow=True 表示的是将提取到的链接继续作用到 链接提取器上再次进行新提取的链接页面的链接提取
Rule(link, callback='parse_item', follow=False ),
Rule(link1, callback='parse_item', follow=False ),
)
# 数据解析:解析提取链接请求所返回的响应
def parse_item(self, response):
tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
for tr in tr_list:
title = tr.xpath('./td[2]/a[2]/text()').extract_first()
status = tr.xpath('./td[3]/span/text()').extract_first()
detail_url = tr.xpath('./td[2]/a[2]/@href').extract_first()
item = SunPaItem2()
item['title']=title
item['status']=status
yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
# 解析详情页中的新闻内容
def parse_detail(self, response):
item = response.meta['item']
content = response.xpath('/html/body/div[9]/table[2]//tr[1]//text()').extract()
content = ''.join(content)
item['content'] = content
yield item
22. 基于分布式的爬虫
1. 实现方式:scrapy+scrapy_redis组件实现的分布式。scrapy+redis
2. 原生的scrapy是不可以实现分布式的!!!
3. 什么是分布式
- 需要搭建一个由n台电脑组成的机群,然后在每一台电脑中执行同一组程序,让其对同一个网络资源
进行联合且分布的数据爬取。
4. 为什么scrapy不可以实现分布式
- 调度器不可以被共享
- 管道不可以被共享
5. scrapy-reids组件的作用是什么
- 提供可以被共享的管道和调度器
分布式的实现流程
1. 环境的安装 : pip install scrapy-redis
2. 创建工程 : scrapy startproject ProName
3. cd 工程
4. 爬虫文件的创建的两种方式
1. 基于Spider : scrapy genspider spiderName
2. 基于CrawSpider : scrapy genspider -t crawl spiderName www.xxx.com
5. 修改爬虫文件
1. 导包 :
1. 基于CrawlSpider爬虫 : from scrapy_redis.spiders import RedisCrawlSpider
2. 基于Spider爬虫文件 : from scrapy_redis.spiders import RedisSpider
2. 将当前爬虫类的父类修改为 RedisCrawlSpider
3. 删除allowed_domains 和 start_urls
4. 添加一个redis_key = 'xx'的属性,表示的是调度器队列的名称
5. 根据常规形式编写爬虫代码
6. 修改settings配置文件
1. 指定管道
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
2. 指定调度器
1. 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重 的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
2. 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
3. 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如 果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True
3. 指定redis
REDIS_HOST = '192.168.13.254' #需要存储redis数据的电脑的ip
REDIS_PORT = 6379
7. 修改redis的配置文件(redis.windows.conf)
1. 关闭默认绑定
注释掉 56行中的 : bind 127.0.0.1 # 注释掉后任何人都可以访问redis数据库
2. 关闭保护模式
75行 : protected-mode no # 不关闭的话,其他电脑访问只能读数据,不能写数据
8. 启动redis的服务端(携带配置文件)和客户端
启动服务端的代码 :redis-server.exe redis.windows.conf
9. 启动分布式的爬虫程序
1. cd 爬虫文件所对应的目录中
2. 执行命令 : scrapy runspider 爬虫文件.py 执行后服务就开始监听
3. 向调度器的队列放一个起始url (队列是存在于客户端的redis中的) : redis客户端 : lpush sun www.xx.com (www.xx.com是起始url)
4. redis中就可以查看爬取 的数据了
5. redis的requests是已经爬取过的url列表
10. 实现机群分布式爬取
1. 修改redis的配置文件(redis.windows.conf)
2. 启动redis的服务端(携带配置文件)和客户端
3. cd 爬虫文件所对应的目录中
执行命令 :
scrapy runspider 爬虫文件.py
等待放入起始url
爬虫代码:
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from fbsPro.items import FbsproItem
from scrapy_redis.spiders import RedisCrawlSpider
from scrapy_redis.spiders import RedisSpider
class FbsSpider(RedisCrawlSpider):
name = 'fbs'
# allowed_domains = ['www.xxx.com']
# start_urls = ['http://www.xxx.com/']
redis_key = 'sun' #可被共享的调度器队列的名称
rules = (
Rule(LinkExtractor(allow=r'type=4&page=\d+'), callback='parse_item', follow=True),
)
def parse_item(self, response):
tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
for tr in tr_list:
title = tr.xpath('./td[2]/a[2]/text()').extract_first()
status = tr.xpath('./td[3]/span/text()').extract_first()
item = FbsproItem()
item['title'] = title
item['status'] = status
yield item
items
import scrapy
class FbsproItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
status = scrapy.Field()
settings配置
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
#指定管道
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
#指定调度器
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True
#指定redis
REDIS_HOST = '192.168.13.254'
REDIS_PORT = 6379
23. 基于Celery的分布式/Pypperteer(异步的scrapy)
基于Celery的分布式/Pypperteer(异步的scrapy)
python芹菜 - Celery
24. 增量式
增量式
- 概念:监测
- 核心技术:去重
- 适合使用增量式的网站:
- 基于深度爬取
- 对爬取过的页面的url进行一个记录(记录表)
- 基于非深度爬取
- 记录表:爬取过的数据对应的数据指纹
- 数据指纹:就是原始数据的一组唯一标识
- 所谓的记录表是以怎样的形式存在于哪?
- redis的set充当记录表
基于CrawlSpider的增量式爬虫示例 - 基于深度爬取
1. 新建工程
scrapy startproject pachong
→ cd pachong
→ scrapy genspider -t crawl pachong_py www.xx.com
2. 配置settings
- 配置UA
- 配置Rebots
- 配置日志等级
- 开启相应管道
3. 写爬虫代码
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis # 导入redis模块
from pachong.items import PachongItem
class PachongPySpider(CrawlSpider):
name = 'pachong_py'
# allowed_domains = ['www.xx.com']
start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html']
conn = Redis(host='127.0.0.1',port=6379) # 实例化redis管道
rules = (
Rule(LinkExtractor(allow=r'page/\d+\.html'), 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:
movie_name = li.xpath('./div/div/h4/a/text()').extract_first()
detail_url = 'https://www.4567tv.tv'+ li.xpath('./div/div/h4/a/@href').extract_first()
# 向redis插入数据,movie_url为自定义的数据字段
ex = self.conn.sadd('movie_url',detail_url)
# redis插入数据有返回值,若返回值是1,说明redis的集合set中没有此条新数据,若为0,说明有此条数据,不能插入
if ex == 1: # 返回值为1 ,则说明没有爬取过,然后执行回调爬取
print('有更新,正在爬取...')
item = PachongItem()
item['title'] = movie_name
yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
else: # 爬取过,不需要再次爬取
print('没有更新的电影数据!!')
def parse_detail(self,response):
item = response.meta['item']
content = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
item['content'] = content
yield item
4. 写items
import scrapy
class PachongItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
5. 进入管道进行持久化存储 (存入redis) - 注意开管道
class PachongPipeline(object):
def process_item(self, item, spider):
conn = spider.conn
conn.lpush('movie_data',item)
return item
25. 反爬机制
- 反爬机制
- robots
- UA伪装
- 图片懒加载
- 验证码
- cookie
- 动态加载的数据
- 动态变化的请求参数
- js加密
- js混淆
- 代理