-
什么是爬虫?
-
通过编写程序让其模拟浏览器上网,然后去互联网中抓取数据的过程
-
-
爬虫的分类
-
通用爬虫:就是抓取一整张页面源码内容。
-
聚焦爬虫:抓取的是页面中局部的内容
-
增量式爬虫:可以监测网站数据更新的情况。抓取网站中最新更新出来的数据。
-
-
反爬机制:对应的载体数网站。
-
反反爬策略:对应的载体爬虫程序。
-
探究一下爬虫的合法性:
-
爬虫本身是不被法律禁止(中立性)
-
爬取数据的违法风险的体现:
-
爬虫干扰了被访问网站的正常运营
-
爬虫抓取了受到法律保护的特定类型的数据或信息。
-
-
如何规避违法的风险?
-
严格遵守网站设置的robots协议;
-
在规避反爬虫措施的同时,需要优化自己的代码,避免干扰被访问网站的正常运行;
-
在使用、传播抓取到的信息时,应审查所抓取的内容,如发现属于用户的个人信息、隐私或者他人的商业秘密的,应及时停止并删除。
-
-
-
第一个反爬机制:robots协议
-
特性:防君子不防小人
-
https和http相关
-
http协议:客户端和服务器端进行数据交互的形式。
-
常用的请求头信息
-
User-Agent:请求载体的身份标识
-
Connection:close
-
-
响应头信息
-
content-type
-
-
https:安全的http(加密)
-
对称秘钥加密
-
非对称秘钥加密
-
证书秘钥加密(***):证书
-
requests模块
-
作用:模拟浏览器发请求。
-
编码流程:
-
指定url
-
发起请求,获取响应对象
-
获取响应数据
-
持久化存储
-
通用爬虫小练习
1.简易的网页采集器
import requests
url = 'https://www.sogou.com/web'
# 动态的参数
wd = input('enter a key word:')
param = { 'query':wd}
# 携带了动态参数进行的请求发送
response = requests.get(url=url,params=param)
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伪装:
-
需要将User-Agent对应的数据封装到字典种,将字典作用的请求方法的headers参数中
-
# 简易的网页采集器 url = 'https://www.sogou.com/web' # 动态参数 msg = input('enter a key word:') params = { 'query':msg} headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",} # 携带了动态参数进行的请求发送 reponse = requests.get(url=url, params=params,headers=headers) #可以手动修改响应数据的编码 response.encoding = 'utf-8' page_text = reponse.text filename = msg+'.html' with open(filename,'w',encoding='utf8') as f: f.write(page_text)
2. 爬取豆瓣电影中电影详情数据
import requests url = 'https://movie.douban.com/j/chart/top_list' headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36", } start = input('起始爱情电影:') limit = input('TOP爱情电影:') params = { "type":"13", "interval_id": "100:90", "action": "" , "start": start, "limit":limit, } page_text = requests.get(url=url,params=params,headers=headers).json() for dic in page_text: title,score = dic['title'],dic['score'] print('电影名:{},评分:{}'.format(title,score))
动态加载的数据
-
需要借助于抓包工具进行分析和动态加载数据对应url的提取
3. 爬取肯德基餐厅的位置信息
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword' city = input('请输入要查询城市的名称:') headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36", } for i in range(1,8): data = { "cname":"", "pid": "", "keyword": city, "pageIndex": i, "pageSize": "10", } page_json = requests.post(url=url,headers=headers,data=data).json() # print(page_json) for dic in page_json['Table1']: rownum = dic['rownum'] storeName = dic['storeName'] addressDetail = dic['addressDetail'] print('{}-{}-{}'.format(rownum,storeName,addressDetail))
4. 化妆品公司详情爬取
http://125.35.6.84:81/xk/ # 1.检测页面中的数据是否为动态加载 # 2.通过抓包工具找到动态加载数据对应的数据包,数据包中提取该请求的url # 3.发现首页中动态加载的数据包中并没有详情页的url,但是有每一家企业的id # 4.通过观察发现,每一家企业详情页的域名是一致的只有携带的id参数不同 # 5.可以通过相同的域名结合着不同企业的id组成一个企业详情页的url # 6.通过抓包工具发现详情页中的企业详情数据是动态加载出来的 # 7.在抓包工具中通过全局搜索找到了动态加载数据对应的数据包,并且可以提取url # 8.多家企业对应的详情数据对应的数据包的url都是一样的,只有携带的参数id的值不同 url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList' headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36' } for page in range(1,11): data = { "on": "true", "page": str(page), "pageSize": "15", "productName": "", "conditionType": "1", "applyname": "", "applysn": "", } json_data = requests.post(url=url,data=data,headers=headers).json() print('第{}页爬取结束!'.format(page)) for dic in json_data['list']: _id = dic.get('ID') #对企业详情数据对应的url发起一个post请求 post_url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById' data = {'id':_id} detail_data = requests.post(url=post_url,headers=headers,data=data).json() company_name = detail_data['epsName'] print(company_name)
5.爬取喜马拉雅的免费音频
import requests url = 'https://www.ximalaya.com/revision/play/album?' headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'} for num in range(1,10): params = { "albumId": "9742789", "pageNum": num, "sort": "0", "pageSize": "30", } json_data = requests.get(url=url,headers=headers,params=params).json() for dic in json_data.get('data').get('tracksAudioPlay'): trackName = dic['trackName'] trackUrl = dic['trackUrl'] src = dic['src'] url = 'https://www.ximalaya.com{}'.format(trackUrl) print(url) xs_data = requests.get(url=src,headers=headers).content # print(xs_data) with open('./{}.mp3'.format(trackName),'wb') as fp: fp.write(xs_data)
-
实现数据解析的方式:
-
正则
-
bs4
-
xpath
-
pyquery
-
-
为什么要使用数据解析?
-
数据解析是实现聚焦爬虫的核心技术,就是在一张页面源码中提取出指定的文本内容。
-
-
数据解析的通用解析原理?
-
我们要解析提取的数据都是存储在标签中间或者是标签的属性中
-
1.标签定位
-
2.取文本或者取
-
一、正则解析
1. 案例 爬糗事百科糗图
// 通过检查源码发现目标div标签 <div class="thumb"> <a href="/article/121960143" target="_blank"> <img src="//pic.qiushibaike.com/system/pictures/12196/121960143/medium/5E2GWJ5SP18QQ051.jpg" alt="重要通知"> </a> </div>
import re import requests import os from urllib import request # 通用的url模板不可变的 url = 'https://www.qiushibaike.com/pic/page/%d/?s=5205111' if not os.path.exists('./qiutuPic'): os.mkdir('./qiutuPic') headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"} for page in range(1,36): # 新的url地址 new_url = format(url%page) # 重新发送请求 page_text = requests.get(url=new_url,headers=headers).text # 数据解析:img的src的属性值(图片的连接) ex = '<div class="thumb">.*?<img src="(.*?)" alt=.*?</div>' img_src_list = re.findall(ex,page_text,re.S) # 在这里使用正则的时候,必须有re.S参数 for src in img_src_list: src = 'https:'+src img_name = src.split('/')[-1] img_path = './qiutuPic/'+img_name request.urlretrieve(src,img_path) print(img_name+'下载成功!')
二、bs4 解析
-
解析原理:
-
1.实例化一个
BeautifulSoup
的一个对象,且将即将被解析的页面源码加载到该对象中 -
2.需要调用
bs
对象中相关属性和方法进行标签定位和数据的获取
-
-
环境安装
-
pip install lxml
(解析器) -
pip install bs4
-
-
BeautifulSoup对象的实例化
-
BeautifulSoup('fp','lxml')
:将本地存储的一张HTML页面中的页面源码加载到bs4对象中 fp文件句柄 -
BeautifulSoup(page_text,'lxml')
:将互联网请求到的页面源码数据加载到bs4对象
-
bs
相关属性和方法
-
soup.tagName
:可以定位到第一次出现的tagName
标签,返回值是一个单数 -
f
ind('tagName') == soup.taagName
-
属性定位:find('tagName',attrName='value'),返回的也是单数
-
find_all():和find的用法一样,只是返回值是一个列表
-
select('选择器'):id,class,标签,层级选择器,返回值为列表.>表示一个层级 空格表示多个层级
-
取文本:string定位的是直系的文本内容,text,get_text()定位的是所有文本内容 两个获得东西是一样的
-
取属性:tag['attrName']
二、bs4案例
1. 基础
from bs4 import BeautifulSoup #使用bs解析本地存储的一张页面中相关的局部数据 fp = open('./test.html','r',encoding='utf-8') soup = BeautifulSoup(fp,'lxml') #标签的定位 soup.a soup.find('div',class_='tang') soup.find_all('div') soup.select('.song') soup.select('.tang > ul > li > a') soup.select('.tang > ul > li > a')[1].string soup.find('div',class_='song').text soup.find('div',class_='song').get_text() soup.select('.song > img')[0]['src']
2. 爬取小说案例
-
爬取诗词名句网中的西游记小说
from bs4 import BeautifulSoup import requests url = 'http://www.shicimingju.com/book/xiyouji.html' headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"} page_text = requests.get(url,headers=headers).text # 数据解析 soup = BeautifulSoup(page_text,'lxml') a_list = soup.select('.book-mulu > ul > li > a') # 或者可以用 ./book-mulu li > a fp = open('./西游记.edub','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 # 解析详情页的页面源码 soup = BeautifulSoup(detail_page_text,'lxml') content = soup.find('div',class_='chapter_content').text fp.write(title+':'+content+' ') print(title,'下载成功!!') fp.close()
三、xpath解析
1.介绍
-
优点:通用性强
-
解析原理:
-
1.实例化一个etree的对象,将即将被解析的页面加载到该对象中
-
2.调用etree对象中的xpath方法结合着不同的xpath表达式实现标签定位和数据提取
-
-
环境安装:
-
pip install lxml
-
-
etree对象实例化:
-
etree.parse('filePath')
-
etree.HTML(page_text)
-
2. xpath表达式
-
xpath方法返回值是列表.
-
最左侧如果为一个斜杠,则表示必须从跟节点开始进行标签定位
-
最左侧为两个斜杠,表示可以从任意位置标签定位
-
非最左侧的一个斜杠表示一个层级,两个斜杠表示多个层级
-
属性定位://tagName[@attrName='value']
-
索引定位://div[@class="tang"]/ul/li[2] 索引是从1开始
-
取文本: /text() //text()
-
取属性:/@attrName
3. xpath案例
1 .boss爬虫岗位爬取
from lxml import etree import requests headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"} url = 'https://www.zhipin.com/c101010100/?query=python爬虫&page=%d' for page in range(1,5): new_url = format(url%page) page_text = requests.get(url=new_url,headers=headers).text # 解析 tree = etree.HTML(page_text) li_list = tree.xpath('//*[@id="main"]/div/div[3]/ul/li') for li in li_list: job_name = li.xpath('./div/div[@class="info-primary"]/h3/a/div[1]/text()')[0] salary = li.xpath('./div/div[@class="info-primary"]/h3/a/span/text()')[0] company = li.xpath('./div/div[2]/div/h3/a/text()')[0] detail_url = 'https://www.zhipin.com'+li.xpath('./div/div[1]/h3/a/@href')[0] detail_page_text = requests.get(url=detail_url,headers=headers).text # 详情页工作描述 tree = etree.HTML(detail_page_text) job_desc = tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()') job_desc = ''.join(job_desc) print(job_name,salary,company,job_desc)
-
中文乱码问题
#通用的url模板(不可变) url = 'http://pic.netbian.com/4kdongwu/index_%d.html' for page in range(1,11): if page == 1: new_url = 'http://pic.netbian.com/4kdongwu/' else: new_url = format(url%page) response = requests.get(new_url,headers=headers) # response.encoding = 'utf-8' page_text = response.text #解析:图片名称和图片的地址 tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="slist"]/ul/li') for li in li_list: img_title = li.xpath('./a/img/@alt')[0] #通用解决中文乱码的处理方式 img_title = img_title.encode('iso-8859-1').decode('gbk') img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]
3.xpath之管道符|
#爬取https://www.aqistudy.cn/historydata/热门城市和全部城市的城市名称 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()') # print(all_cities) #好处:使得xpath表达式更具有通用性 tree.xpath('//div[@class="bottom"]/ul/li/a/text() | //div[@class="bottom"]/ul/div[2]/li/a/text()')
# 糗事百科中爬取作者名 匿名的处理 url = 'https://www.qiushibaike.com/text/page/%d/' for page in range(1,10): print(str(page)+'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') new_url = format(url%page) page_text = requests.get(url=new_url,headers=headers).text tree = etree.HTML(page_text) div_list = tree.xpath('//div[@id="content-left"]/div') for div in div_list: author = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()')[0] print(author)
4. 图片懒加载
-
图片懒加载是一种网页优化技术。图片作为一种网络资源,在被请求时也与普通静态资源一样,将占用网络资源,而一次性将整个页面的所有图片加载完,将大大增加页面的首屏加载时间。为了解决这种问题,通过前后端配合,使图片仅在浏览器当前视窗内出现时才加载该图片,达到减少首屏图片请求数的技术就被称为“图片懒加载”。
-
网站一般如何实现图片懒加载技术呢?
-
在网页源码中,在img标签中首先会使用一个“伪属性”(通常使用src2,original......)去存放真正的图片链接而并非是直接存放在src属性中。当图片出现到页面的可视化区域中,会动态将伪属性替换成src属性,完成图片的加载。
import requests from lxml import etree url = 'http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html' page_text = requests.get(url,headers=headers).text for page in range(1,10): if page == 1: new_url = 'http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html' tree = etree.HTML(page_text) div_list = tree.xpath('//div[@id="container"]/div') for div in div_list: img_src = div.xpath('./div/a/img/@src2')[0] print(img_src)
5. 爬取简历并持久化存储
import os import requests from lxml import etree from urllib import request if not os.path.exists('./简历'): os.mkdir('./简历') url = 'http://sc.chinaz.com/jianli/free_%d.html' headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"} for page in range(1,20): if page == 1: new_url = 'http://sc.chinaz.com/jianli/free.html' else: new_url = format(url%page) page_text = requests.get(url=new_url,headers=headers).text tree = etree.HTML(page_text) div_list = tree.xpath('//*[@id="container"]') for div in div_list: a_href = div.xpath('./div/a/@href')[0] a_name = div.xpath('./div/p/a/text()')[0] a_name = a_name.encode('iso-8859-1').decode('utf8') detail_text = requests.get(url=a_href,headers=headers).text tree = etree.HTML(detail_text) detail_url = tree.xpath('//*[@id="down"]/div[2]/ul/li[1]/a/@href')[0] jianli_name = a_name jianli_path = './简历/'+jianli_name+'.rar' request.urlretrieve(detail_url,jianli_path) print(jianli_name+'下载成功!')
-
代理
-
cookie
-
模拟登陆
-
验证码的识别
-
-
线程池在requests的应用
-
单线程+多任务异步协程
-
代理: 代理服务器
-
基于代理的网站:
-
站大爷
-
goubanjia
-
快代理
-
西祠代理
-
-
代理的匿名度
-
透明:使用透明代理,对方服务器可以知道你使用了代理,并且也知道你的真实IP
-
匿名:对方服务器可以知道你使用了代理,但不知道你的真实IP。
-
高匿:对方服务器不知道你使用了代理,更不知道你的真实IP
-
-
类型:
-
http:代理服务器只可以转发http协议的请求
-
https:代理服务器只可以转发https协议的请求
-
-
编码:
-
在get或者post方法中应用一个proxies的参数,给其参数赋值为{'http':'ip:port'}
-
cookie的处理方式
-
处理方式:
-
手动处理:将cookie的键值对手动添加到headers字典中,然后将headers作用到get或者post方法的headers参数中
-
自动处理:使用Session。
-
session作用:session可以和requests一样进行请求的发送
-
特性:使用session发起请求,如果请求过程中产生cookie,则cookie会被自动存储到session中
-
-
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=-1&count=10&category=-1' #创建一个session对象 session = requests.Session() #获取cookie session.get('https://xueqiu.com/',headers=headers) #携带cookie进行的请求发送 session.get(url,headers=headers).json()
模拟登陆
-
什么是模拟登陆
-
使用requests对登陆按钮的请求进行发送
-
-
为什么要模拟登陆
-
有的页面必须登陆之后才显示
-
-
验证码的识别
-
线上的打码平台:超级鹰,云打码,打码兔......
-
-
超级鹰的使用流程
-
注册:用户中心身份的账户
-
登陆:
-
查看提分的剩余
-
创建一个软件ID:软件ID-》生成一个软件ID(ID的名称和说明)
-
下载示例代码:开发文档-》选择语言-》点击下载
-
-
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() #识别古诗文网中的验证码图片 url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36' } page_text = requests.get(url,headers=headers).text tree = etree.HTML(page_text) code_img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] img_data = requests.get(code_img_src,headers=headers).content with open('./code.jpg','wb') as fp: fp.write(img_data) #使用线上平台识别验证码 chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') #用户中心>>软件ID 生成一个替换 96001 im = open('code.jpg', 'rb').read() # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要// print(chaojiying.PostPic(im, 1902)['pic_str'])
1.案例-基于古诗文网的模拟登陆
#识别古诗文网中的验证码图片 s = requests.Session() url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = s.get(url,headers=headers).text tree = etree.HTML(page_text) code_img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] #使用session对验证码图片发请求(会产生cookie) img_data = s.get(code_img_src,headers=headers).content with open('./code.jpg','wb') as fp: fp.write(img_data) # 解析出动态参数的数据值 __VIEWSTATE = tree.xpath('//input[@id="__VIEWSTATE"]/@value')[0] __VIEWSTATEGENERATOR = tree.xpath('//input[@id="__VIEWSTATEGENERATOR"]/@value')[0] #使用线上平台识别验证码 chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') # 用户中心>>软件ID 生成一个替换 96001 im = open('code.jpg', 'rb').read() # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要// #验证码图片的文本数据 code_img_text = chaojiying.PostPic(im, 1902)['pic_str'] print(code_img_text) #模拟登陆 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_img_text, "denglu": "登录", } login_page_text = s.post(login_url,headers=headers,data=data).text with open('gushiwen.html','w',encoding='utf-8') as fp: fp.write(login_page_text)
线程池的应用
- 异步操作可以和非异步操作结合使用
- 线程池最好只被应用在较为耗时的操作中
同步:
def request(url): print('正在请求:',url) time.sleep(2) print('请求成功:',url) start = time.time() urls = [ 'www.1.com', 'www.2.com', 'www.3.com', 'www.4.com', ] for url in urls: request(url) print(time.time()-start)
基于异步的操作:
from multiprocessing.dummy import Pool start = time.time() pool = Pool(4) def request(url): print('正在请求:',url) time.sleep(2) print('请求成功:',url) urls = [ 'www.1.com', 'www.2.com', 'www.3.com', 'www.4.com', ] pool.map(request,urls) pool.close() pool.join() print(time.time()-start)
2.案例-爬取梨视频的短视频数据
import requests from lxml import etree import re #爬取梨视频的短视频数据 #var contId="1570697",liveStatusUrl="liveStatus.jsp",liveSta="", # playSta="1",autoPlay=!1,isLiving=!1,isVrVideo=!1,hdflvUrl="",sdflvUrl="",hdUrl="",sdUrl="",ldUrl="", # srcUrl="https://video.pearvideo.com/mp4/adshort/20190626/cont-1570697-14061078_adpkg-ad_hd.mp4",vdoUrl=srcUrl,skinRes="//www.pearvideo.com/domain/skin",videoCDN="//video.pearvideo.com"; pool = Pool(4) url = 'https://www.pearvideo.com/category_1' page_text = requests.get(url,headers=headers).text tree = etree.HTML(page_text) li_list = tree.xpath('//*[@id="listvideoListUl"]/li') all_video_urls = [] for li in li_list: detail_url = 'https://www.pearvideo.com/'+li.xpath('./div/a/@href')[0] detail_page_text = requests.get(detail_url,headers=headers).text #解析出视频的url ex = 'srcUrl="(.*?)",vdoUrl' video_url = re.findall(ex,detail_page_text,re.S)[0] all_video_urls.append(video_url) def dowmload_save(url): video_data = requests.get(url,headers=headers).content fileName = url.split('/')[-1] with open(fileName,'wb') as fp: fp.write(video_data) print(fileName,'下载成功') #视频数据的请求和持久化存储是比较耗时的,可以基于线程池来实现 pool.map(dowmload_save,all_video_urls)#参数1对应的函数必须只可以有一个参数
多任务异步协程(并发)
- 协程:对象。如果一个函数在定义的时候被async修饰了,则该函数被调用的时候会返回一个协程对象
,函数内部的程序语句不会其调用的时候被执行(特殊的函数)
- 任务对象:对象,就是对协程的进一步封装。任务对象==协程==特殊的函数.任务对象中可以显示
协程的相关状态。任务对象可以被绑定一个回调。
- 绑定回调:
- 事件循环:无限(不确定循环次数)的循环。需要向其内部注册多个任务对象(特殊的函数)。
- async:专门用来修饰函数
- await:挂起
- requests和aiohttp 的区别
- session.get、post(url,headers,params/data,proxy="http://ip:port") ### aiohttp
- response.text()、json().read()
import aiohttp import requests import asyncio headers = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36" } url = 'https://www.ximalaya.com/revision/play/album?albumId=11219576&pageNum=1&sort=1&pageSize=30' json_data = requests.get(url, headers=headers).json() urls = [] # 30个音频的url和name for dic in json_data['data']['tracksAudioPlay']: audio_url = dic['src'] audio_name = dic['trackName'] urls.append({'name': audio_name, 'url': audio_url}) # 定义特殊的修饰函数用来发送请求 async def request(dic): async with aiohttp.ClientSession() as s: # s是aiohttp中的一个请求对象 # await:阻塞操作对应的代码中(请求,获取响应数据) # async:只要是跟aiohttp相关联的代码前 # proxy='http://ip:port' 代理操作 async with await s.get(dic['url'], headers=headers) as response: # text()字符串形式的响应数据 # json(),read()二进制类型的数据 audio_data = await response.read() name = dic['name'] return {'data': audio_data, 'name': name} # 回调函数的封装:必须有一个task的参数 def saveData(task): print('开始保存') dic = task.result() # 音频的数据和名字 async修饰函数的返回值 filename = dic['name'] + '.m4a' # data = dic['data'] with open(f'./相声/{filename}', 'wb') as f: f.write(data) print(filename, '下载完成!!') tasks = [] # 任务列表 for dic in urls: # 协程 c = request(dic) # 任务对象 task = asyncio.ensure_future(c) task.add_done_callback(saveData) tasks.append(task) # 创建事件循环对象,然后进行任务对象的注册,且启动事件循环 loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
-
概念:模块。基于浏览器自动化的模块。
-
爬虫之间的关联
-
实现模拟登陆
-
非常便捷的获取动态加载的页面数据
-
-
环境的安装:pip install selenium
-
selenium的基本使用
-
实例化一个浏览器对象(必须将浏览器的驱动程序进行加载)
-
通过代码指定相关的行为动作
-
from selenium import webdriver from time import sleep # 实例化了一个谷歌浏览器对象且将驱动程序进行了加载 bro = webdriver.Chrome(executable_path='./chromedriver.exe') # 发起请求 bro.get('https://hellojoy.jd.com/') # 使用find系列的函数进行标签定位 input_tag = bro.find_element_by_id('key') # 进行节点交互 input_tag.send_keys('macbook') sleep(2) # 搜索按钮的定位 btn = bro.find_element_by_xpath('//*[@id="search-2014"]/div/button') btn.click() sleep(10) # 执行js,实现滑动窗口 bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(2) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(2) bro.close()
# 动态请求数据 from bs4 import BeautifulSoup bro = webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('http://125.35.6.84:81/xk/') # 获取当前页面中展示的企业名称 # 获取浏览器打开页面的页面源码数据(可见即可爬) page_text = bro.page_source # 使用bs4 解析企业名称 soup = BeautifulSoup(page_text,'lxml') dl_list = soup.select('#gzlist > li > dl') for dl in dl_list: name = dl.string print(name) sleep(2) bro.close()
#处理多页 from selenium import webdriver from time import sleep #实例化了一个谷歌浏览器对象且将驱动程序进行了加载 bro = webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('http://125.35.6.84:81/xk/') alist = [] #存放不同页码对应的页面源码数据(page_source) #获取当前页面中展示的企业名称 sleep(2) #获取浏览器打开页面的页面源码数据(可见即可爬) page_text = bro.page_source alist.append(page_text) id_value_model = 'pageIto_first%d' for page in range(2,8): id_value = format(id_value_model%page) btn = bro.find_element_by_id(id_value) btn.click() sleep(3) page_text = bro.page_source alist.append(page_text) sleep(2) for page_text in alist: sleep(1) soup = BeautifulSoup(page_text,'lxml') dl_list = soup.select('#gzlist > li > dl') for dl in dl_list: name = dl.string print(name) bro.quit()
#模拟登陆 from selenium import webdriver from time import sleep #实例化了一个谷歌浏览器对象且将驱动程序进行了加载 bro = webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('https://qzone.qq.com/') #分析发现定位的a标签是出现在iframe标签之下,则必须通过switch_to.frame操作后,才可以进行标签定位 bro.switch_to.frame('login_frame') a_tag = bro.find_element_by_id('switcher_plogin') a_tag.click() sleep(2) bro.find_element_by_id('u').send_keys('123456') sleep(2) bro.find_element_by_id('p').send_keys('XXXXXXXXXXXXX') sleep(2) bro.find_element_by_id('login_button').click() #登陆成功后的页面源码数据 page_text = bro.page_source sleep(2) bro.quit()
不太常用的操作
-
动作链
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') #基于动作链实现拖动操作 bro.switch_to.frame('iframeResult') #定位即将被拖动的标签 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(15,0).perform() sleep(0.5) action.release() sleep(2) bro.quit()
-
-
谷歌无头浏览
from selenium import webdriver from time import sleep 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.runoob.com/try/try.php?filename=jqueryui-api-droppable') bro.save_screenshot('./1.png') print(bro.page_source)
from selenium.webdriver import Chrome from selenium.webdriver import ChromeOptions option = ChromeOptions() option.add_experimental_option('excludeSwitches', ['enable-automation']) driver = Chrome(executable_path='./chromedriver.exe',options=option)
超级鹰接口
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 selenium import webdriver import time import requests from selenium.webdriver import ActionChains from PIL import Image # Pillow == PIL bro = webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('https://kyfw.12306.cn/otn/login/init') time.sleep(3) # 定位到了img标签(验证码),想要通过img标签获取验证码图片的左上角和右下角两点坐标 code_img_ele = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') time.sleep(3) # 获取验证码图片的左上角和右下角两点坐标 location = code_img_ele.location # 验证码图片左上角坐标 print(location, ':左上角坐标!') size = code_img_ele.size # 返回的是验证码的尺寸(长和宽) print(size, ':size的值') # 矩形区域:表示的就是验证码图片的区域(裁剪的区域) rangle = ( int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height'])) # 将浏览器打开的登录页面进行整体截图 bro.save_screenshot('aa.png') i = Image.open('./aa.png') # 验证码图片的名称 code_img_name = 'code.png' # 根据制定好的矩形区域(左上和右下两点坐标)进行验证码图片的裁剪 frame = i.crop(rangle) frame.save(code_img_name) chaojiying = Chaojiying_Client('martin144', 'martin144', '900215') # 用户中心>>软件ID 生成一个替换 96001 im = open('./code.png', 'rb').read() result = chaojiying.PostPic(im, 9004)['pic_str'] # x1,y1 x1,y1|x2,y2 55,99 # x1,y1|x2,y2 ==》 [[x1,y1],[x2,y2]] all_list = [] # 存储的是超级鹰返回的坐标数据 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) print(all_list) # 根据all_list中的数据进行点击操作 action = ActionChains(bro) for l in all_list: x = l[0] y = l[1] action.move_to_element_with_offset(code_img_ele, x, y).click().perform() time.sleep(1) bro.find_element_by_id('username').send_keys('123ertghjk') # 12306账号 time.sleep(2) bro.find_element_by_id('password').send_keys('asdfghjka') # 12306密码 time.sleep(2) bro.find_element_by_id('loginSub').click() time.sleep(10) bro.quit()