爬虫
day01
- 什么是anacanda
- 基于数据分析+ML(机器学习)的一个集成环境
- 什么是jupyter
- 就是anacanda提供的一个编辑环境(基于浏览器)
- cell有两种模式
- code
- markdown
快捷键
-
插入cell:a,b
-
删除cell:x
-
执行cell:shift+enter
-
切换cell的模式:m,y
-
tab:自动补全
-
shift+tab:打开帮助文档
-
学习方法
- 总结结论
- user-agent:请求载体的身份标识
- 重复
- 讲
- 总结结论
爬虫
-
requeests
-
数据解析
-
如何提升爬取数据的效率
- 线程池
- 多任务的异步携程
-
scrapy
- 分布式
-
基于移动端数据的爬取
-
selenium
数据分析:
- 将数据的价值最大化
ML
- 特征工程
- 机器学习的基础
- 回归模型
- 分类模型
- 集成学习
算法+数据结构
-
stack
-
队列
-
链表
-
二叉树
-
二分查找
-
选择,冒泡
-
插入
-
希尔
-
快速
-
什么是爬虫
- 通过编写程序模拟浏览器发请求,让其去互联网中爬取数据的过程。
-
爬虫分类
- 通用爬虫:抓取一整张页面源码的数据
- 聚焦爬虫:抓取一张页面中局部的数据
- 通用爬虫和聚焦爬虫之间的关联:
- 聚焦需要建立在通用爬虫的基础之上
- 通用爬虫和聚焦爬虫之间的关联:
- 增量式:监测网站数据更新的情况。以便将最新更新出来的数据进行爬取。
-
爬虫的合法性探究
-
爬虫所带来风险主要体现在以下2个方面:
- 爬虫干扰了被访问网站的正常运营;
- 爬虫抓取了受到法律保护的特定类型的数据或信息。
-
那么作为爬虫开发者,如何在使用爬虫时避免进局子的厄运呢?
-
严格遵守网站设置的robots协议;
-
在规避反爬虫措施的同时,需要优化自己的代码,避免干扰被访问网站的正常运行;
-
在使用、传播抓取到的信息时,应审查所抓取的内容,如发现属于用户的个人信息、隐私或者他人的商业秘密的,应及时停止并删除。
-
-
-
反爬机制
- robots协议:纯文本的协议。
- User-Agent:请求载体的身份标识
- 不遵从
- robots协议:纯文本的协议。
-
反反爬策略
requests基本介绍
-
作用:实现爬虫。模拟浏览器上网。
-
编码流程:
- 指定url
- 发起请求
- 获取响应数据
- 持久化存储
-
环境的安装:pip install reqeusts
-
将搜狗首页的页面源码数据进行爬取
import requests
#1.指定url
url = 'https://www.sogou.com/'
#2,发起请求
#get方法返回的是一个响应对象
response = requests.get(url=url)#根据指定的url发起get请求
#3.获取响应数据
page_text = response.text #test返回的是字符串形式的响应数据
#4.持久化存储
with open('./sogou.html','w',encoding='utf-8') as fp:
fp.write(page_text)
- 简易的网页采集器
#请求参数动态化
wd = input('enter a key word:')
params = {
#url携带的请求参数
'query':wd
}
url = 'https://www.sogou.com/web'
#params参数:实现参数动态化
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,'下载成功!!!')
- 上述程序出问题:
- 出现了乱码
- 数据量级不吻合
#处理乱码问题
wd = input('enter a key word:')
params = {
#url携带的请求参数
'query':wd
}
url = 'https://www.sogou.com/web'
#params参数:实现参数动态化
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检测
- 反反爬策略:UA伪装
#UA检测
wd = input('enter a key word:')
params = {
#url携带的请求参数
'query':wd
}
headers = {
#需要修改的请求头信息
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
url = 'https://www.sogou.com/web'
#params参数:实现参数动态化
response = requests.get(url=url,params=params,headers=headers)#UA伪装进行的请求发送
#手动修改响应数据的编码格式
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,'下载成功!!!')
enter a key word:tom
tom.html 下载成功!!!
- 需求:爬取豆瓣电影中爱情类型中的电影详情数据
- 动态加载的数据:通过另一个请求单独请求到的数据
- 如何检测爬取的数据是否为动态加载的数据?
- 基于抓包工具进行局部搜索
- 抓包工具捕获到所有的数据包,然后找到浏览器地址栏url对应的数据包,response这个选项卡中进行局部搜索(搜到,搜不到)。
- 搜不到:数据为动态加载
- 基于抓包工具进行全局搜索
- 搜到:数据不是动态加载
- 直接对浏览器地址栏的url发起请求获取数据即可
- 搜不到:数据为动态加载
- 抓包工具捕获到所有的数据包,然后找到浏览器地址栏url对应的数据包,response这个选项卡中进行局部搜索(搜到,搜不到)。
- 基于抓包工具进行局部搜索
import requests
#UA检测会被应用在绝大数的网站中
headers = {
#需要修改的请求头信息
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
params = {
"type": "13",
"interval_id": "100:90",
"action": "",
"start": "10",
"limit": "200",
}
url = 'https://movie.douban.com/j/chart/top_list'
response = requests.get(url=url,headers=headers,params=params)
data_list = response.json()
for dic in data_list:
name = dic['title']
score = dic['score']
print(name,score)
- 需求:将餐厅的位置信息进行爬取
#捕获到了第一页对应的位置信息
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
city = input('enter a city name:')
data = {
"cname": '',
"pid": '',
"keyword": city,
"pageIndex": "1",
"pageSize": "10",
}
pos_list = requests.post(url=url,headers=headers,data=data).json()['Table1']#data参数是用来实现参数动态化
for dic in pos_list:
pos = dic['addressDetail']
print(pos)
#捕获所有页码对应的位置信息
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
city = input('enter a city name:')
#全站数据的爬取
for pageNum in range(1,9):
data = {
"cname": '',
"pid": '',
"keyword": city,
"pageIndex": str(pageNum),
"pageSize": "10",
}
pos_list = requests.post(url=url,headers=headers,data=data).json()['Table1']#data参数是用来实现参数动态化
for dic in pos_list:
pos = dic['addressDetail']
print(pos)
-
需求:所有企业的企业详情数据
-
分析:
- 1.每一家企业对应的详情数据是动态加载出来的
- 2.通过抓包工具进行了全局搜索,定位到了动态加载数据对应的数据包
- 提取出url:每一家企业对应的url都一样
- 请求方式:都一样
- 请求参数:都为id但是参数值不同
- id就是每一家企业的唯一标识
- 如果可以将每一家企业的唯一标识id值捕获到,最终需求就可以完成
- 对企业id值的捕获
- 在首页中进行分析,使用抓包工具进行了企业名称的全局搜索,定位到的数据包的数据中就包含了企业名称和对应的id值
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById'
data = {
'id':'3718324df7b94fb782f7e9bb5d185552'
}
requests.post(url=url,headers=headers,data=data).json()
#捕获企业id
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList'
ids = []
for pageNum in range(1,6):
data = {
"on": 'true',
'page': str(pageNum),
'pageSize': '15',
'productName': '',
'conditionType': '1',
'applyname': '',
'applysn': '',
}
company_list = requests.post(url=url,headers=headers,data=data).json()['list']
for dic in company_list:
_id = dic['ID']
ids.append(_id)
#完整实现
#1.捕获到所有企业的id
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList'
ids = []
for pageNum in range(1,6):
data = {
"on": 'true',
'page': str(pageNum),
'pageSize': '15',
'productName': '',
'conditionType': '1',
'applyname': '',
'applysn': '',
}
company_list = requests.post(url=url,headers=headers,data=data).json()['list']
for dic in company_list:
_id = dic['ID']
ids.append(_id)
#2.基于存储id的列表ids进行循环的请求发送
detail_url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById'
for _id in ids:
data = {
'id':_id
}
company_data = requests.post(url=detail_url,headers=headers,data=data).json()
legalPerson = company_data['legalPerson']
address = company_data['epsProductAddress']
print(legalPerson,address)
- 总结
- requests的基本使用
- 编码流程
- 搜狗首页数据爬取==》流程
- 参数的动态化
- UA伪装
- 修改响应数据的编码格式
- get/post:
- url
- headers
- data/params
- 动态加载的数据
- 全站数据的爬取
- requests的基本使用
day02
回顾
- 问题:
- ip被封:代理
- 请求参数问题:
- 动态变化的请求参数
- 加密的请求参数
- 响应数据的问题:
- cookie
- 请求参数
- 加密:
- js逆向
- 重点内容
- 参数的动态化
- data/prames
- 反爬机制:
- robots.txt
- UA检测
- 动态加载的数据
- 如何检测数据是否为动态加载
- 如何捕获动态加载的数据
- 动态加载的数据是如何生成?
- ajax
- js
- 参数的动态化
数据解析
- 作用:实现聚焦爬虫
- 实现方式:
- 正则
- bs4:重点
- xpath:重点
- pyquery:自学
- 数据解析的通用原理是什么?
- 解析的一定是html页面的源码数据
- 标签中存储的文本内容
- 标签属性的属性值
- 原理:
- 标签定位
- 取文本或者取属性
- 解析的一定是html页面的源码数据
- 爬虫实现的流程
- 指定url
- 发请求
- 获取响应数据
- 数据解析
- 持久化存储
import requests
import re
headers = {
#需要修改的请求头信息
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
正则解析
单字符:
. : 除换行以外所有字符
[] :[aoe] [a-w] 匹配集合中任意一个字符
\d :数字 [0-9]
\D : 非数字
\w :数字、字母、下划线、中文
\W : 非\w
\s :所有的空白字符包,括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S : 非空白
数量修饰:
* : 任意多次 >=0
+ : 至少1次 >=1
? : 可有可无 0次或者1次
{m} :固定m次 hello{3,}
{m,} :至少m次
{m,n} :m-n次
边界:
$ : 以某某结尾
^ : 以某某开头
分组:
(ab)
贪婪模式: .*
非贪婪(惰性)模式: .*?
re.I : 忽略大小写
re.M :多行匹配
re.S :单行匹配
re.sub(正则表达式, 替换内容, 字符串)
import re
#提取出python
key="javapythonc++php"
#####################################################################
#提取出hello world
key="<html><h1>hello world<h1></html>"
#####################################################################
#提取170
string = '我喜欢身高为170的女孩'
#####################################################################
#提取出http://和https://
key='http://www.baidu.com and https://boob.com'
#####################################################################
#提取出hit.
key='bobo@hit.edu.com'#想要匹配到hit.
#####################################################################
#匹配sas和saas
key='saas and sas and saaas'
#####################################################################
key="javapythonc++php"
re.findall('python',key)
#提取出hello world
key="<html><h1>hello world<h1></html>"
re.findall('<h1>(.*?)<h1>',key)
#提取170
string = '我喜欢身高为170的女孩'
re.findall('\d+',string)
#提取出http://和https://
key='http://www.baidu.com and https://boob.com'
re.findall('https?://',key)
#提取出hit.
key='bobo@hit.edu.com'#想要匹配到hit.
re.findall('h.*?\.',key)
#匹配sas和saas
key='saas and sas and saaas'
re.findall('sa{1,2}s',key)
- 需求:使用正则将http://duanziwang.com/category/%E6%90%9E%E7%AC%91%E5%9B%BE/ 对应的段子的标题取出
url = 'http://duanziwang.com/category/搞笑图/'
#捕获到的是字符串形式的响应数据
page_text = requests.get(url=url,headers=headers).text
#数据解析
ex = '<div class="post-head">.*?<a href="http://duanziwang.com/\d+\.html">(.*?)</a></h1>'
re.findall(ex,page_text,re.S)#爬虫使用正则做解析的话re.S必须要使用
#持久化存储
bs4解析
-
环境安装:
- pip install bs4
- pip install lxml
-
解析原理
- 1.实例化一个BeautifulSoup的对象,需要将等待被解析的页面源码数据加载到该对象中
- 2.需要调用BeautifulSoup对象中相关的属性和方法进行标签定位和文本数据的提取
-
BeautifulSoup如何实例化
- 方式1:BeautifulSoup(fp,'lxml'),将本地存储的一张html文件中的指定数据进行解析
- 方式2:BeautifulSoup(page_text,'lxml'),将从互联网中爬取到的数据直接进行数据解析
-
标签定位
- soup.tagName:定位到第一个出现的tagName标签
- 属性定位:根据指定的属性进行对应标签的定位
- soup.find('tagName',attrName='value')
- soup.find_all('tagName',attrName='value')
- 选择器定位:
- soup.select('选择器')
- 层级选择器:
- 大于号:表示一个层级
- 空格:表示多个层级
-
取文本
- tag.string:取出直系的文本内容
- tag.text:取出所有的文本内容
-
取属性
- tag['attrName']
from bs4 import BeautifulSoup
fp = open('./test.html','r',encoding='utf-8')
soup = BeautifulSoup(fp,'lxml')
soup
soup.div
# soup.find('div',class_='song')
# soup.find('a',id='feng')
# soup.find_all('div',class_='song')
# soup.select('.tang')
# soup.select('#feng')
soup.select('.tang li')
soup.p.string
soup.p.text
soup.find('div',class_='tang').text
a_tag = soup.select('#feng')[0]
a_tag['href']
- 将三国演义整片小说内容进行爬取且持久化存储
main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
page_text = requests.get(url=main_url,headers=headers).text
fp = open('./sanguo.txt','w',encoding='utf-8')
#数据解析
soup = BeautifulSoup(page_text,'lxml')
#解析出章节的标题&详情页的url
a_list = soup.select('.book-mulu > ul > li > a')
for a in a_list:
title = a.string
detail_url = 'http://www.shicimingju.com'+a['href']
#捕获章节内容
page_text_detail = requests.get(detail_url,headers=headers).text#获取了详情页的页面源码数据
#数据解析:章节内容
detail_soup = BeautifulSoup(page_text_detail,'lxml')
div_tag = detail_soup.find('div',class_='chapter_content')
content = div_tag.text
fp.write(title+':'+content+'\n')
print(title,'下载成功!!!')
fp.close()
- 图片数据的爬取
- 基于requests
- 基于urllib
- 区别:能不能实现UA伪装
#基于requests
url = 'http://pic.netbian.com/uploads/allimg/190902/152344-1567409024af8c.jpg'
img_data = requests.get(url=url,headers=headers).content#content返回的是二进制类型的数据
with open('./123.png','wb') as fp:
fp.write(img_data)
#基于urllib
from urllib import request
request.urlretrieve(url=url,filename='./456.jpg')
xpath解析
- 环境安装:
- pip install lxml
- 解析原理
- 实例化一个etree的对象,且将被解析的页面源码数据加载到该对象中
- 使用etree对象中的xpath方法结合着不同形式的xpath表达式进行标签定位和数据的提取
- 实例化对象:
- etree.parse('filePath'):将本地存储的一个html文件中的数据加载到实例化好的etree对象中
- etree.HTML(page_text)
- xpath表达式
-
标签定位:
- 最左侧的/:必须从根节点开始定位标签(几乎不用)
- 非最左侧的/:表示一个层级
- 最左侧的//:可以从任意位置进行指定标签的定位(最常用)
- 非最左侧的//:表示多个层级
- 属性定位://tagName[@attrName="value"]
- 索引定位://tagNamne[index],索引是从1开始
- //div[contains(@class, "ng")]
- //div[starts-with(@class, "ta")]
-
取文本
- /text():取出直系的文本内容
- //text():取出的是所有的文本
-
取属性
- /@attrName
-
from lxml import etree
tree = etree.parse('./test.html')
tree.xpath('/html/head/meta')
tree.xpath('/html//meta')
tree.xpath('//meta')
tree.xpath('/meta')#error
tree.xpath('//p')
tree.xpath('//div[@class="tang"]')
tree.xpath('//li[1]')
tree.xpath('//a[@id="feng"]/text()')[0]
tree.xpath('//div[@class="tang"]//text()')
tree.xpath('//a[@id="feng"]/@href')
- 需求:使用xpath解析图片地址和名称且将图片进行下载保存到本地
重点:局部解析的时候./表示的含义
import os
url = 'http://pic.netbian.com/4kmeinv/'
page_text = requests.get(url=url,headers=headers).text
dirName = 'imgLibs'
if not os.path.exists(dirName):
os.mkdir(dirName)
#数据解析
tree = etree.HTML(page_text)
#xpath是基于一整张页面源码进行数据解析
img_list = tree.xpath('//div[@class="slist"]/ul/li/a/img')
#局部数据解析
for img in img_list:
#./表示的是当前标签,xpath的调用者就是当前标签
img_src = 'http://pic.netbian.com'+img.xpath('./@src')[0]
img_name = img.xpath('./@alt')[0]+'.jpg'
img_name = img_name.encode('iso-8859-1').decode('gbk')
filePath = './'+dirName+'/'+img_name
request.urlretrieve(img_src,filename=filePath)
print(img_name,'下载成功!!!')
#全站操作
#1.指定一个通用的url模板:用来生成不同页码对应的url,模板是不可变
url = 'http://pic.netbian.com/4kmeinv/index_%d.html'
dirName = 'imgLibs'
if not os.path.exists(dirName):
os.mkdir(dirName)
for pageNum in range(1,6):
if pageNum == 1:
page_url = 'http://pic.netbian.com/4kmeinv/'
else:
page_url = format(url%pageNum)
page_text = requests.get(url=page_url,headers=headers).text
#数据解析
tree = etree.HTML(page_text)
#xpath是基于一整张页面源码进行数据解析
img_list = tree.xpath('//div[@class="slist"]/ul/li/a/img')
#局部数据解析
for img in img_list:
#./表示的是当前标签,xpath的调用者就是当前标签
img_src = 'http://pic.netbian.com'+img.xpath('./@src')[0]
img_name = img.xpath('./@alt')[0]+'.jpg'
img_name = img_name.encode('iso-8859-1').decode('gbk')
filePath = './'+dirName+'/'+img_name
request.urlretrieve(img_src,filename=filePath)
print(img_name,'下载成功!!!')
day03
- 自己完成的任务
- pyquery
- 标签定位和数据的提取
- urllib
- 如何进行请求的发送
- pyquery
requests模块高级
-
代理
-
模拟登陆
- 验证码
- cookie
-
代理(反爬机制)
- 概念:代理服务器
- 代理服务器的作用:
- 拦截请求和响应,进行转发
- 代理和爬虫之间的关联是什么?
- 如果pc端的ip被禁掉后,我们就可以使用代理的机制更换请求的ip
- 如何获取相关的代理服务器:
- 快代理
- 西祠代理
- goubanjia
- 代理精灵:推荐。http://http.zhiliandaili.cn/
- 匿名度:
- 透明:知道你使用了代理也知道你的真实ip
- 匿名:对方服务器知道你使用了代理机制,但是不知道你的真实ip
- 高匿:对方服务器不知道你使用了代理机制更不知道你的真实ip
- 类型:
- http:只可以拦截转发http协议的请求
- https:只可以转发拦截https的请求
-
基本测试:代理的作用
- 基于百度搜索ip,搜索到的页面中会显示该请求对应的ip地址
- 语法结构
- get/post(proxies={'http/https':'ip:port'})
import requests
from lxml import etree
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
url = 'https://www.sogou.com/web?query=ip&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=623&sst0=1578274254338&lkt=2%2C1578274253715%2C1578274253785&sugsuv=000AE8ACDDDAD0EB5DE49630C6588161&sugtime=1578274254338'
#代理机制对应的就是get或者post方法中的一个叫做proxies的参数
page_text = requests.get(url=url,headers=headers,proxies={'https':'14.134.184.156:39347'}).text
print(page_text)
- 构建一个代理池
import random
#构建了一个代理池
all_ips = []
url = 'http://t.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=21&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2'
page_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
datas = tree.xpath('//body//text()')
for data in datas:
dic = {
'https':data
}
all_ips.append(dic)
all_ips[0]
#将西祠代理中的免费代理ip进行爬取
url = 'https://www.xicidaili.com/nn/%d'#定义了一个通用的url模板
datas = [] #存储解析到的所有的数据
for pageNum in range(1,16):
new_url = format(url%pageNum)
page_text = requests.get(url=new_url,headers=headers,proxies=random.choice(all_ips)).text
tree = etree.HTML(page_text)
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]
dic = {
'https':ip+':'+port
}#{'https':'ip:port'}
datas.append(dic)
print(len(datas))
模拟登陆
- 为什么需要实现模拟登陆
- 相关的页面是必须经过登陆之后才可现
- 验证码的处理
- 使用相关的打码平台进行验证码的动态识别
- 打码平台:
- 超级鹰(推荐)http://www.chaojiying.com/about.html
- 软件id:899370
- 云打码
- 超级鹰(推荐)http://www.chaojiying.com/about.html
- 超级鹰的使用流程:
- 注册【用户中心】身份的账号
- 登陆
- 创建一个软件
- 下载示例代码
- cookie的处理
#下载好的示例代码
#!/usr/bin/env python
# coding:utf-8
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()
# if __name__ == '__main__':
# chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001') #用户中心>>软件ID 生成一个替换 96001
# im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
# print chaojiying.PostPic(im, 1902) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
#自己封装一个识别验证码的函数
def transformCode(imgPath,imgType):
chaojiying = Chaojiying_Client('fh0821', '54874428', '903087')
im = open(imgPath, 'rb').read()
return chaojiying.PostPic(im,imgType)['pic_str']
- cookie的处理
- 手动处理
- 将请求携带的cookie封装到headers中
- 自动处理
- session对象。该对象和requests都可以进行get和post的请求的发送。如果使用session对象在进行请求发送过程中,产生了cookie,则cookie会被自动存储到session对象中。如果cookie存储到了session对象中后,再次使用session进行请求发送,则该次请求就是携带者cookie发送的请求。
- 在使用session处理cookie的时候,session对象最少需要发送几次请求?
- 两次。第一次请求是为了获取和存储cookie,第二次请求才是携带cookie进行的请求发送。
- 手动处理
#需求:爬取雪球网https://xueqiu.com/,中的新闻标题和内容
#实例化一个session对象
session = requests.Session()
#试图将cookie获取且存储到session对象中
session.get('https://xueqiu.com/',headers=headers)
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20361817&count=15&category=-1'
session.get(url=url,headers=headers).json()#携带cookie进行的请求发送
- 古诗文网模拟登陆
- url:https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx
- 分析
- 通过抓包工具定位到点击登陆按钮对应的数据包(包含用户名,密码,验证码)
- 从数据包中取出请求的url,请求方式,请求参数
session = requests.Session()
#验证码的识别
first_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = session.get(first_url,headers=headers).text
#解析出验证码图片的src的属性值
tree = etree.HTML(page_text)
code_img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
#对验证码图片地址进行请求发送
code_img_data = session.get(code_img_src,headers=headers).content#产生了cookie
with open('./code.jpg','wb') as fp:
fp.write(code_img_data)
#使用基于超级鹰实现的函数进行验证码的识别
code_img_text = transformCode('./code.jpg',1902)
print(code_img_text)
url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
'__VIEWSTATE': 'WQlcl775IHe1sjsai6HoahnPHU79MSS7ZyjzWaONdzwJSlR76Ndfm9mikHQVMTSwHZKIQ+fC8SeVGIU6b5AIb1spoHSnuoINERq+PdbI7vv/3+qaXK10HtmIw7w=',
'__VIEWSTATEGENERATOR': 'C93BE1AE',
'from': 'http://so.gushiwen.org/user/collect.aspx',
'email': 'www.zhangbowudi@qq.com',
'pwd': 'bobo328410948',
'code': code_img_text,
'denglu': '登录',
}
#点击登陆按钮对应的请求发送操作
page_text = session.post(url=url,headers=headers,data=data).text
with open('./gushiwen.html','w',encoding='utf-8') as fp:
fp.write(page_text)
-
遇到了动态变化的请求参数如何处理?
- 通常情况下,动态变化的请求参数的值都会被隐藏在前台页面中
- 基于抓包工具进行全局搜索(基于js逆向获取相关的数据值)
-
回顾
- 代理
- 概念
- 作用
- 请求转发
- 实现:
- get/post(proxies={'https':'ip:port'})
- 代理池:大列表
- 验证码的识别
- 打码平台
- cookie
- 手动处理
- 自动处理:session
- 动态变化的请求参数
- 前台页面
- 全局搜索
- 代理
-
总结反爬机制:
- robots
- UA检测
- 动态加载数据的捕获
- 代理
- 验证码
- 动态变化的请求参数
- cookie
- 图片懒加载
- 伪属性
-
需求:爬取文本数据将其通过百度ai的语音合成功能将文字合成成音频文件,将将音频文件部署到flask服务器中进行播放
from aip import AipSpeech
""" 你的 APPID AK SK """
APP_ID = '17272609'
API_KEY = 'h3Grp2xXGG0VeSAKlDL9gc4Q'
SECRET_KEY = 'WEEeDpICnzifwBAGF4QW4QiGgSb1u3ND'
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
#文本数据的爬取
url = 'http://duanziwang.com/category/%E4%B8%80%E5%8F%A5%E8%AF%9D%E6%AE%B5%E5%AD%90/2/'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
article_list = tree.xpath('/html/body/section/div/div/main/article')
fileName = 1
for article in article_list:
fileName += 1
content = article.xpath('./div[1]/h1/a/text()')[0]
#数据爬取到了,基于语音合成将其转换成音频文件
result = client.synthesis(content, 'zh', 1, {
'vol': 5,
'per':4,
'spd':3,
})
# 识别正确返回语音二进制 错误则返回dict 参照下面错误码
if not isinstance(result, dict):
filePath = str(fileName)+'.mp3'
with open(filePath, 'wb') as f:
f.write(result)
print(filePath,'写入成功!!!')
day04
#演示代码
from selenium import webdriver
from time import sleep
# 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
driver = webdriver.Chrome(r'./chromedriver.exe')
# 用get打开百度页面
driver.get("http://www.baidu.com")
# 查找页面的“设置”选项,并进行点击
driver.find_elements_by_link_text('设置')[0].click()
sleep(2)
# # 打开设置后找到“搜索设置”选项,设置为每页显示50条
driver.find_elements_by_link_text('搜索设置')[0].click()
sleep(2)
# 选中每页显示50条
m = driver.find_element_by_id('nr')
sleep(2)
m.find_element_by_xpath('//*[@id="nr"]/option[3]').click()
m.find_element_by_xpath('.//option[3]').click()
sleep(2)
# 点击保存设置
driver.find_elements_by_class_name("prefpanelgo")[0].click()
sleep(2)
# 处理弹出的警告页面 确定accept() 和 取消dismiss()
driver.switch_to_alert().accept()
sleep(2)
# 找到百度的输入框,并输入 美女
driver.find_element_by_id('kw').send_keys('美女')
sleep(2)
# 点击搜索按钮
driver.find_element_by_id('su').click()
sleep(2)
# 在打开的页面中找到“Selenium - 开源中国社区”,并打开这个页面
driver.find_elements_by_link_text('美女_百度图片')[0].click()
sleep(3)
# 关闭浏览器
driver.quit()
selenium
-
概念:基于浏览器自动化的模块
- appnium:基于手机应用的自动化模块
-
环境的安装:
- pip install selenium
-
爬虫之间的关联?
- 实现模拟登陆
- 便捷帮我们捕获到动态加载的数据(可见即可得)
-
selenium的基本操作
- 必须提供对应浏览器的驱动程序
- 谷歌浏览器驱动程序下载地址:
- 实例化一个浏览器对象
- bro = webdriver.Chrome(executable_path='驱动程序的路径')
- 标签定位:
- find系列的函数
- senk_keys():向制定标签中录入数据
- click()
- js注入:execute_script('jsCode')
- page_source:返回当前页面的页面源码数据(包含动态加载数据)
-
缺点:
- 数据爬取的效率低下
from selenium import webdriver
from time import sleep
#实例化浏览器对象
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
#制定一些自动化的操作
#发起请求
bro.get('https://www.jd.com/')
#标签定位
search_tag = bro.find_element_by_id('key')
#向文本框中录入数据
search_tag.send_keys('mac pro')
sleep(2)
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')
btn.click()
sleep(2)
#js注入execute_script('jsCode')
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
#page_source:返回当前页面的页面源码数据(包含动态加载数据)
print(bro.page_source)
#关闭浏览器
bro.quit()
from lxml import etree
#使用selenium捕获药监总局的动态加载数据
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('http://125.35.6.84:81/xk/')
sleep(1)
#第一页的页面源码数据
page_text = bro.page_source
all_page_text = [page_text]
for i in range(1,4):
a_tag = bro.find_element_by_xpath('//*[@id="pageIto_next"]')
a_tag.click()
sleep(1)
page_text = bro.page_source
all_page_text.append(page_text)
for page_text in all_page_text:
tree = etree.HTML(page_text)
li_list = tree.xpath('//*[@id="gzlist"]/li')
for li in li_list:
name = li.xpath('./dl/@title')[0]
print(name)
bro.quit()
线程池,异步协程
-
线程池
- 导包:from multiprocessing.dummy import Pool
- pool.map(callback,alist)
- 让callback可以异步将alist中的列表元素进行某种形式的操作
- 注意事项:callback必须要有一个参数、
-
主要是被应用在耗时的操作
-
单线程+多任务的异步协程
-
特殊函数
- 如果一个函数的定义被async关键字修饰后,则该函数就是一个特殊的函数。
- 特殊之处:
- 该函数被调用后函数内部的实现语句不会被立即执行。
- 该函数会返回一个协程对象
-
协程:
- 对象。当特殊的函数被调用后,该函数就会返回一个协程对象。
- 协程对象 == 特殊函数
-
任务对象
- 就是对协程对象的进一步封装(就是一个高级的协程对象)
- 任务对象协程对象特殊函数
- 绑定回调:
- task.add_done_callback(funcName)
- funName这个回调函数必须要带一个参数,这个参数表示的就是当前的任务对象
- 参数.result():表示的就是当前任务对象对应的特殊函数的返回值
-
事件循环对象:
- 创建事件循环对象
- asyncio.get_event_loop()
- 需要将任务对象注册到该事件循环对象中且启动事件循环
- loop.run_until_complete(task)
- 创建事件循环对象
-
等待(await):当阻塞操作结束后让loop回头执行阻塞之后的代码
-
挂起(wait()):将当前的任务对象交出cpu的使用权
-
【重点注意事项】:
-
-
在特殊函数实现内部不可以出现不支持异步的模块代码,否则会中断整个的异步效果
-
aiohttp:
-
requests不支持异步,不可以出现在特殊函数内部。
-
aiohttp:支持异步的网络请求模块
- pip install aiohttp
-
代码的编写:
-
写出基本架构
with aiohttp.ClientSession() as sess:
#with sess.get/post(url=url,headers=headers,data/params,proxy="http://ip:port") as response:
with sess.get(url=url) as response:
#text():获取字符串形式的响应数据
#read():获取bytes类型的响应数据
page_text = response.text()return page_text
-
补充细节
- 在每一个with前加上async
- 在每一个阻塞操作前加上await关键字
- 代码参照完整代码
-
-
完整代码:
async with aiohttp.ClientSession() as sess:with sess.get/post(url=url,headers=headers,data/params,proxy="http://ip:port") as response:
async with await sess.get(url=url) as response:
#text():获取字符串形式的响应数据read():获取bytes类型的响应数据
page_text = await response.text()
return page_text
-
-
-
解析到带html标签的文本内容
- bs4
day05
回顾
-
单线程+多任务的异步协程
- 特殊的函数
- 调用后实现内部的程序语句不会被立即执行
- 调用后返回一个协程对象
- 协程对象
- 协程特殊函数一组指定的操作
- 任务对象
- 高级的协程对象
- 绑定回调函数
- task.add_done_callback(func)
- func:
- 必须要有一个参数(当前的任务对象)
- 参数.result()表示的就是特殊函数的返回值
- 任务对象 == 一组指定的操作
- 事件循环对象
- 创建一个eventloop对象
- 作用:
- 必须要装载一个或者多个任务对象(任务对象是需要注册到eventloop)
- 启动事件循环对象
- 可以异步的执行其内部注册的每一个任务对象对应的指定操作
- 等待await:确保eventloop一定会执行阻塞操作
- 挂起:让当前发生阻塞的任务对象交出cpu的使用权
- asyncio.wait(tasks)
- 重点:
- 特殊函数内部不可以出现不支持异步模块的代码
- aiohttp:支持异步的网络请求模块
- 使用使用上下文机制(with...as)
- 实例化一个请求对象(ClientSession())
- get/post()进行请求发送。(阻塞操作)
- proxy参数:'http://ip:port'
- 获取响应数据(阻塞操作)
- response.text():字符串
- response.read():byte
- 特殊的函数
-
selenium
- 动作链
- 无头浏览器
- 规避检测
- 浏览器托管
- 12306的模拟登陆
-
动作链
- from selenium.webdriver import ActionChains
- NoSuchElementException:没有定位到指定的标签
- 定位的标签是存在于一张嵌套的子页面中,如果想定位子页面中的指定标签的话需要:
- bro.switch_to.frame('iframe标签id的属性值'):将当前浏览器页面切换到指定的子页面的范围中
- 定位的标签是存在于一张嵌套的子页面中,如果想定位子页面中的指定标签的话需要:
- 针对指定的浏览器实例化一个动作链对象
- action = ActionChains(bro)
- action.click_and_hold(tagName)
- move_by_offset(10,15)
- perform()是的动作链立即执行
from selenium import webdriver
from selenium.webdriver import ActionChains
from time import sleep
# 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
bro = webdriver.Chrome('./chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
#标签定位
bro.switch_to.frame('iframeResult')
div_tag = bro.find_element_by_id('draggable')
#需要使用ActionChains定制好的行为动作
action = ActionChains(bro)#针对当前浏览器页面实例化了一个动作链对象
action.click_and_hold(div_tag)#点击且长按指定的标签
for i in range(1,7):
action.move_by_offset(10,15).perform()#perform()是的动作链立即执行
sleep(0.5)
bro.quit()
- 无头浏览器
- 没有可视化界面的浏览器
- phantomJS:无头浏览器
- 谷歌无头浏览器:
- 就是你本机安装的谷歌浏览器,只是需要通过代码进行相关配置就可以变成无头浏览器
from selenium.webdriver.chrome.options import Options
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.baidu.com/')
sleep(1)
bro.save_screenshot('./1.png')#截屏
print(bro.page_source)
selenium规避检测
-
- 浏览器托管
- 环境配置:
- 1.本机谷歌浏览器驱动程序所在的目录的路径添加到环境变量中
- 2.使用本机谷歌的驱动程序开启一个浏览器
- chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenum\AutomationProfile"
- 9222:端口(任意)
- "C:\selenum\AutomationProfile":已经事先存在的一个空目录
- chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenum\AutomationProfile"
- 使用如下代码接管目前打开的浏览器:
- 环境配置:
- 浏览器托管
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)#代码托管代开的浏览器,不会实例化一个新的浏览器。
bro.get('https://kyfw.12306.cn/otn/login/init')
12306模拟登陆
- url:https://kyfw.12306.cn/otn/login/init
- 分析:
- 识别的验证码图片必须通过截图获取然后存储到本地
- 登陆操作和唯一的验证码图片一一对应
- 识别的验证码图片必须通过截图获取然后存储到本地
#pip install Pillow
from PIL import Image
from selenium.webdriver import ActionChains
from selenium import webdriver
#识别验证码的函数
def transformCode(imgPath,imgType):
chaojiying = Chaojiying_Client('13614167787', '13614167787', '903126')
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.find_element_by_id('username').send_keys('xxxxxxx')
bro.find_element_by_id('password').send_keys('12345465')
#验证码的点击操作
bro.save_screenshot('main.png')#将页面当做图片保存到本地
#将单独的验证码图片从main.png中裁剪下载
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
# print(location,size)
#裁剪的范围(验证码图片左下角和右上角两点坐标)
rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height']))
#使用Image类根据rangle裁剪范围进行验证码图片的裁剪
i = Image.open('./main.png')
frame = i.crop(rangle)#验证码对应的二进制数据
frame.save('./code.png')
result = transformCode('./code.png',9004)#99,71|120,140
#99,71|120,140 == [[99,71],[120,140]]
all_list = []#[[99,71],[120,140]]
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 data in all_list:
x = data[0]#11
y = data[1]#22
ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform()
sleep(1)
sleep(2)
bro.find_element_by_id('loginSub').click()
bro.quit()
#下载好的示例代码
#!/usr/bin/env python
# coding:utf-8
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()
# if __name__ == '__main__':
# chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001') #用户中心>>软件ID 生成一个替换 96001
# im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
# print chaojiying.PostPic(im, 1902) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
移动端数据的捕获
-
三方的抓包工具
- fiddler(window)
- 青花瓷(mac)
- miteproxy
-
抓包工具就是代理服务器
- 拦截和转发请求and响应
-
fiddler的基本使用
- 安装
- 默认情况下装好的fiddler只可以捕获到http协议的请求
- 配置:
- 进行fiddler证书的本机安装
- tools->options->HTTPS->
- tools->options->HTTPS->
- 进行fiddler证书的本机安装
- 安装
-
配置fiddler让其可以捕获其它设备发起的请求
- 将fiddler重新启动
-
需要在手机中安装好fiddler的证书
- 将fiddler所在的机器和手机设定到一个网段下(pc开启一个热点手机连接)
- 在手机的浏览器中访问:fiddler所在机器的ip:fidder的端口(192.168.1.123:8887),就可以看到如下页面(提供了一个证书下载的功能):
-
将下载好的证书信任且安装到手机中
-
在手机中配置代理
- 配置的代理ip:fiddler所在机器的ip地址
- 代理端口:fiddler自己的端口
js加密、解密、混淆
-
分析:
- 空气指标的数据是动态加载出来
- 修改了搜索条件后点击搜索按钮会发起ajax请求,请求到我们想要的指标数据。
- 从上一步定位到的数据包中提取出url,请求方式,请求参数
- url和请求方式可以拿来直接用
- 请求参数是动态变化且加密
- 响应数据也是加密的密文数据
- 空气指标的数据是动态加载出来
-
找到点击搜索按钮发起的ajax请求对应的代码
- 基于火狐浏览器的开发者工具找到搜索按钮对应点击事件绑定的js函数是哪个
- getData(),该函数的实现
- type=HOUR:以小时为单位进行数据的查询
- 调用了另两个函数:getAQIData(), getWeatherData()
- 并没有找到ajax请求对应的的代码
- 分析getAQIData&getWeatherData:
- 这两个函数的实现几乎一致,唯一的区别是
- var method = 'GETDETAIL';
- var method = 'GETCITYWEATHER';
- 也没有找到ajax请求对应的代码,但是发现了另一个函数的调用:
- getServerData(method, param, function(obj),0.5 )
- method:
- 'GETDETAIL'
- 'GETCITYWEATHER'
- param是一个字典,有四组键值对:
- city;
- type;
- startTime;
- endTime;
- method:
- getServerData(method, param, function(obj),0.5 )
- 分析getServerData函数的实现:
- 基于抓包工具进行全局搜索,定位到了一个指定的数据包,出现了getServerData关键词,这个关键词对应的js代码被加密了
- JS混淆:将js中的核心代码加密
- JS反混淆:
- 暴力破解:
- 分析反混淆后的getServerData函数的实现代码:
- 终于发现了ajax请求对应的代码:
- getParam(method, object)返回动态变化且加密的请求参数d的值。
- method == method
- object == param
- decodeData(data):接受加密的响应数据返回解密后的明文数据
- data:加密的响应数据
- getParam(method, object)返回动态变化且加密的请求参数d的值。
- 终于发现了ajax请求对应的代码:
- js逆向:
- 自动逆向:
- PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
- 一定实现在本机装好nodejs的开发环境
- 我们需要pip install PyExecJS对其进行环境安装。
- PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
- 自动逆向:
- 这两个函数的实现几乎一致,唯一的区别是
- getData(),该函数的实现
- 基于火狐浏览器的开发者工具找到搜索按钮对应点击事件绑定的js函数是哪个
#模拟执行js函数获取动态变化且解密的请求参数d的值
import execjs
node = execjs.get()
# Params
method = 'GETCITYWEATHER'
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函数
print(params)
- 对ajax的url携带d请求参数进行post请求的发送可以获取加密的响应数据
import execjs
import requests
node = execjs.get()
# Params
method = 'GETCITYWEATHER'
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)
#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
print(response_text)
#将密文的响应数据进行解密:模拟调用decodeData(data)
import execjs
import requests
node = execjs.get()
# Params
method = 'GETCITYWEATHER'
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)
#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
# #对加密的响应数据进行解密
jss = 'decodeData("{0}")'.format(response_text)
print(jss)
decrypted_data = ctx.eval(jss)
print(decrypted_data)
day06
回顾
- 问题
- 1.空气参数问题
- 2.代理速度慢
- 3.截图问题
- 分辨率
- selenium
- 动作链
- 浏览器托管
- 规避检测
- 无头浏览器
- phantomJS
- 谷歌无头
- pyppteer
- appnium:
- 基于手机app的自动化的模块
- 和爬虫之间的关联
- 模拟登陆
- 便捷的捕获到动态加载的数据
- js解密
- js混淆
- 对js核心代码进行加密
- js逆向
- 将js代码转换成python代码
- 手动逆向
- 自动逆向:
- PyExcelJS
- js混淆
scrapy框架
- 框架:就是一个具有很强的通用性且封装一些通用实现方法(功能)的一个项目模板。
- scrapy(异步):
- 高性能的网络请求
- 数据解析
- 持久化存储
- 全站数据爬取
- 深度爬取
- 分布式
环境的安装
a. pip3 install wheel
b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
d. pip3 install pywin32
e. pip3 install scrapy
scrapy的基本使用
-
创建一个工程
- scrapy startproject proName
- cd proName
-
创建一个爬虫文件(py源文件)
- scrapy genspider spiderName www.xxx.com
-
基本的配置:settings.py
- UA伪装
- robots协议的不遵从
- 指定日志等级
- LOG_LEVEL = 'ERROR'
-
执行工程
- scrapy crawl spiderName
-
数据解析
- response.xpath('xpath表达式')
- 于etree的不同之处:
- 取文本/属性:返回的是一个Selector对象,文本数据是存储在该对象中,
可以调用extract()/extract_first()取出字符串数据
- 取文本/属性:返回的是一个Selector对象,文本数据是存储在该对象中,
- 常用操作:
- 直接使用xpath返回列表调用extract()/extract_first()
- 如果列表元素只有一个则调用extract_first(),返回的字符串
- 如果列表元素为多个则调用extract(),返回的是列表,列表里装的是字符串
- 直接使用xpath返回列表调用extract()/extract_first()
# -*- coding: utf-8 -*- import scrapy class FirstSpider(scrapy.Spider): #爬虫文件的名称:当前爬虫文件的唯一标识 name = 'first' #允许的域名: # allowed_domains = ['www.baidu.com'] #起始的url列表 #作用:可以将内部的列表元素进行get请求的发送 start_urls = ['http://www.duanziwang.com/'] #数据解析,方法调用的次数是由start_urls元素个数决定 def parse(self, response): article_list = response.xpath('/html/body/section/div/div/main/article') for article in article_list: # title = article.xpath('./div[1]/h1/a/text()').extract_first()#返回字符串 title = article.xpath('./div[1]/h1/a/text()').extract()#返回列表 title = ''.join(title) print(title)
-
持久化存储
- 基于终端指令的持久化存储
- 只可以将parse方法的返回值存储到指定后缀(csv)文本文件中
- 指令:scrapy crawl spiderName -o filePath
class DuanziSpider(scrapy.Spider): name = 'duanzi' # allowed_domains = ['www.xxx.com'] start_urls = ['http://www.duanziwang.com/'] start_urls请求发送的原理 def start_requests(self):#父类方法 for url in self.start_urls: #发起get请求 yield scrapy.Request(url=url,callback=self.parse) 基于终端指令持久化存储 def parse(self, response): article_list = response.xpath('/html/body/section/div/div/main/article') all_datas = [] for article in article_list: title = article.xpath('./div[1]/h1/a/text()').extract_first() content = article.xpath('./div[2]/p//text()').extract() content = ''.join(content) dic = { 'title':title, 'content':content } all_datas.append(dic) return all_datas
- 基于管道的持久化存储
- 实现流程:
1.数据解析(爬虫文件)
2.在item类中定义相关的属性(items.py)
- fieldName = scrapy.Field()
3.将解析的数据存储封装到item类型的对象中(爬虫文件)
- item['fieldName'] = value #给item对象的fieldName属性赋值
4.将item对象提交给管道(爬虫文件)
- yield item #只可以将item提交给优先级最高的管道
5.在管道中接收item,可以将item中存储的数据进行任意形式的持久化存储(pipelines.py)
- process_item():负责接收item对象且对其进行持久化存储
6.在配置文件中开启管道机制
- ITEM_PIPELINES = {
#300:表示的是优先级,数值越小优先级越高
'duanziPro.pipelines.DuanziproPipeline': 300,
}
- 实现流程:
- 细节处理
-
管道文件中的管道类表示的是什么
- 一个管道类对应的就是一种存储形式
- 如果想要实现数据备份,则需要使用多个管道类(多种存储形式=》mysql/redis)
-
process_item中的return item:
- 将item传递给下一个即将被执行的管道类
-
# -*- 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 pymysql from redis import Redis class DuanziproPipeline(object): #重写父类的该方法:该方法只会在爬虫开始的时候执行一次 fp = None def open_spider(self,spider): print('i am open_spider()') self.fp = open('./duanzi.txt','w',encoding='utf-8') def close_spider(self,spider): print('i am close_spider()') self.fp.close() #接收item对象:procee_item方法每调用一次可以接收一个item对象 #item参数:接收到的某一个item对象 def process_item(self, item, spider): title = item['title'] content = item['content'] self.fp.write(title+':'+content+'\n') return item#将item转交给下一个即将被执行的管道类 #数据存储到mysql class MysqlPileLine(object): conn = None cursor = None def open_spider(self,spider): self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123',db='spider',charset='utf8') def process_item(self, item, spider): title = item['title'] content = item['content'] self.cursor = self.conn.cursor() sql = 'insert into duanzi values ("%s","%s")'%(title,content) 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() class RedisPipeLine(object): conn = None def open_spider(self,spider): self.conn = Redis(host='127.0.0.1',port=6379) def process_item(self,item,spider): self.conn.lpush('duanziList',item) #报错:pip install redis==2.10.6
- 基于终端指令的持久化存储
手动get请求发送
- yield scrapy.Request(url,callback)
- url:指定好的请求的url
- callback:callback指定的回调函数一定会被执行(数据解析)
- post请求的发送:
- yield scrapy.FormRequest(url,callback,formdata)
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.duanziwang.com/']
#手动请求的发送:对其他页码的数据进行请求操作
url = 'http://www.duanziwang.com/page/%d/'
pageNum = 2
def parse(self, response):
article_list = response.xpath('/html/body/section/div/div/main/article')
all_datas = []
for article in article_list:
title = article.xpath('./div[1]/h1/a/text()').extract_first()
content = article.xpath('./div[2]/p//text()').extract()
content = ''.join(content)
#实例化item对象
item = DuanziproItem()
#通过中括号的形式访问属性且给其赋值
item['title'] = title
item['content'] = content
yield item
if self.pageNum < 4:
#其他页码的url
new_url = format(self.url%self.pageNum)
self.pageNum += 1
#callback:指定解析的方法是哪一个
yield scrapy.Request(url=new_url, callback=self.parse)
yield在scrapy中的使用
-
向管道提交item对象:yield item
-
手动请求发送:yield scrapy.Request(url,callback)
-
问题:如何将start_urls中的元素进行post请求的发送
- 重写start_requests方法
def start_requests(self):#父类方法
for url in self.start_urls:
#发起get请求
yield scrapy.FormRequest(url=url,callback=self.parse,formdata)
- 重写start_requests方法
scrapy的五大核心组件
引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
day07
内容回顾
- 工程的创建
- cd
- 数据解析
- 请求发送:start_urls
- 函数:parse
- start_requests方法:
- 就是讲start_urls中的url进行get请求的发送
- xpath进行的解析
- etree解析的区别:
- extract():有多个元素的列表中
- extract_first():只有一个元素的列表中
- etree解析的区别:
- 持久化存储
- 基于终端指令
- 只可以将数据存储到指定后缀的文本文件中
- 存储的数据必须为parse方法的返回值
- 指令:scrapy crawl spiderName -o filePath
- 基于管道
- 1.解析数据
- 2.在item类中定义相关的属性
- name = scrapy.Field()
- 3.解析到的数据存储到item类型的对象
- 4.将item提交给管道
- yield item
- 5.在管道中接受item(process_item),进行任意形式的持久化存储
- 6.开启管道
- 注意事项:
- 一个管道类对应什么
- 一个管道类对应一种形式的存储
- process_item=》return item
- 一个管道类对应什么
- 基于终端指令
- 手动请求发送
- get:
- yield scrapy.Request(url,callback)
- post:
- yield scrapy.FormRequest(url,callback)
- get:
- 核心组件
- 引擎
- 事务的触发
- spider
- 产生url,发起请求,数据解析
- 调度器
- 过滤器
- 队列
- 管道
- 下载器
- 引擎
今日内容
请求传参
- 作用:实现深度爬取
- 什么深度爬取:
- 爬取的数据没有存在于同一张页面中
- 在进行手动请求发送的时候可以将一个meta字典传递给callback指定的回调函数
- yield scrapy.Request(url,callback,meta)
- 在回调函数中如何接收meta:
- response.meta['key'] #将meta字典中key对应的value值取出
中间件
- 爬虫中间件:位于引擎和Spider中间
- 下载中间件:位于引擎和下载器中间(推荐)
- 作用:拦截请求和响应
- 为什么需要拦截请求?
- UA伪装(篡改请求的头信息)
- process_request
- request.headers['User-Agent'] = 'xxx'
- process_request
- 设置代理
- process_exception:
- request.meta['proxy'] = 'http://ip:port'
- return request:将修正后的请求对象进行重新发送
- process_exception:
- UA伪装(篡改请求的头信息)
- 为什么需要拦截响应?
- 篡改响应数据
- 网易新闻数据爬取
- 实现流程:
- 1.解析出5个板块对应页面的url
- 2.对五个板块的url发起请求
- 3.获取板块的页面源码数据
- 问题:板块的页面源码数据(响应数据)中没有包含新闻的标题和新闻详情页的url(动态加载的数据)
- 处理:将响应对象的响应数据进行篡改,将其该为包含动态加载数据的响应数据
- selenium帮助我们捕获到包含了动态加载的响应数据
- selenium在scrapy中如何使用
- 实例化一个浏览器对象(爬虫文件中)
- 在中间件的process_response中进行selenium后续的操作
- 在爬虫文件的爬虫类中重写一个closed(self,spider),关闭浏览器
- selenium在scrapy中如何使用
- 实现流程:
作业:
-
将网易新闻的新闻数据进行爬取
- title
- content
-
基于百度AI将新闻的类型和关键字进行提取
-
持久化存储:
-
mysql
- 库表:
- 新闻标题
- 新闻内容
- 新闻类型
- 新闻关键字
- 库表:
-
error:
- \x0x无法被gbk识别
- s.replase('\x0x','')
- \x0x无法被gbk识别
-
day08
scrapy如何爬取图片数据(ImagePileLine)
- 爬虫文件中将二进制资源的url进行爬取和解析,将其存储到item中向管道提交
- 在管道文件中指定对应的管道类
- 父类:from scrapy.pipelines.images import ImagesPipeline
- 配置文件中:
- 自动创建一个指定的文件夹:IMAGES_STORE = './imgsLib'
- 自定义一个基于ImagesPipeline该父类的管道类,重写如下三个方法:
#发起请求
def get_media_requests(self,item,info):
print(item)
img_src = item['img_src']
#请求传参,将meta字典传递给了file_path这个方法
yield scrapy.Request(url=img_src,meta={'item':item})
#定制get_media_request请求到数据持久化存储的路径(文件夹的路径+文件名称)
def file_path(self,request,response=None,info=None):
#通过request.meta接收请求传参传递过来的meta字典
img_name = request.meta['item']['img_name']
return img_name
#将item传递给下一个即将被执行的管道类
def item_completed(self,request,item,info):
return item
CrawlSpider全站数据爬取
- CrawlSpider就是Spider的一个子类
- 如何创建一个基于CrawlSpider的一个爬虫文件
- scrapy genspider -t crawl spiderName www.xxx.com
- LinkExtractor(Allow="正则表达式"):链接提取器
- 作用:根据指定的规则(Allow)进行url的提取
- 注意:Allow=“”:可以将网站的所有链接都捕获到
- Rule(link,callback,fllow=True):规则解析器
- 作用:
- 1.接受到link提取到的链接,然后对其进行请求发送
- 2.根据指定规则对请求到的数据进行数据解析
- follow=True:将链接提取器 继续作用到 链接提取器 提取到的链接 对应的页面源码中
- 深度爬取:
- 让手动请求scrapy.Request()发送,请求传参和LinkExtractor,Rule一起使用实现深度爬取
分布式
-
概念:搭建一个分布式机群,共同执行一组代码,联合对同一个资源的数据进行分布且联合爬取
-
实现方式:
-
简称:scrapy+redis
-
全称:Scrapy框架+scrapy-redis组件
- 原生的scrapy框架是无法实现分布式
- 原生scrapy中的调度器和管道无法被共享
-
scrapy-redis组件的作用
- 可以给原生的scrapy框架提供可以被共享的管道和调度器
-
环境的安装:
-
pip install scrapy-redis
-
实现流程:
-
修改爬虫文件中爬虫类对应的操作
- 1.导包:from scrapy_redis.spiders import RedisCrawlSpider
- 2.将爬虫类的父类修改成RedisCrawlSpider
- 3.将start_url删除,添加一个redis_key=‘可以被共享调度器队列的名称’
- 4.进行常规的请求和解析和向管道提交item的操作即可
-
对settings.py配置
-
配置管道:
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 = 'redis服务的ip地址'
-
REDIS_PORT = 6379
-
redis的配置文件进行修改:redis.windows.conf
- 56行:#bind 127.0.0.1
- 75行:protected-mode no
-
启动redis服务和客户端:
- 携带配置文件启动redis服务:
- redis-server.exe redis.windows.conf
- 启动客户:
- redis-cli
- 携带配置文件启动redis服务:
-
启动程序:
- 在终端中进入到爬虫文件对应的目录中:
- scrapy runspider fbs.py
- 在终端中进入到爬虫文件对应的目录中:
-
向调度器的队列中仍入一个起始的url:
- redis-cli:
- lpush redis_key的属性值 www.xxx.com
- redis-cli:
-
-
增量式爬虫
- 监测网站数据更新的情况。以便于爬取到最新更新出来的数据
- 核心:去重
- 记录表:
- 特性:永久性存储 (redis中的set)作用:
- 记录爬取过的数据对应的url可以以明文的形式存储(url数据长度较短)
- 记录的数据对其生成一个【数据指纹】(url数据长度长)
- 数据指纹就是该组数据的唯一标识