引言
概述
概念:基于网络请求的模块
作用:用来模拟浏览器发请求,从而实现爬虫
通用爬虫
步骤:
- 指定url
- 请求发送:get返回的是一个响应对象
- 获取响应数据: text返回的是字符串形式的响应数据
- 持久化存储
爬取搜狗首页的页面源码数据
1 2 3 4 5 6 7 8 9 10 11
|
import requests
url = 'https://www.sogou.com/'
response = requests.get(url=url)
page_text = response.text page_text
with open('./sogou.html','w',encoding='utf-8') as fp: fp.write(page_text)
|
实现一个简易的网页采集器
- 请求参数动态化(自定义字典给get方法的params传参)
- 使用UA伪装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
url = 'https://www.sogou.com/web'
wd = input('enter a key word:') params = { 'query':wd }
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 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,'爬取成功!')
|
动态加载的数据
- 页面中想要爬取的内容并不是请求当前url得到的,而是通过另一个网络请求请求到的数据(例如,滚轮滑到底部,会发送ajax,对局部进行刷新)
例子:爬取豆瓣电影中动态加载出的电影详情
我们想爬取的页面是:豆瓣电影分类排行榜 - 科幻片 https://movie.douban.com/typerank?type_name=%E7%A7%91%E5%B9%BB&type=17&interval_id=100:90&action=
-
首先在chorme的抓包工具中进行全局搜索发现它不是请求当前url得到的,而是一个新的url: https://movie.douban.com/j/chart/top_list?
-
当然这是一个ajax请求(在chorme的抓包工具中选择 XHR 可以进行查看)
-
在寻找另一部电影,请求的url仍然是 https://movie.douban.com/j/chart/top_list?
-
然后我们就需要找参数的规律了:
1 2 3 4 5 6 7 8 9 10 11
|
type: 17 interval_id: 100:90 action: start: 0 limit: 1
type: 17 interval_id: 100:90 action: start: 20 limit: 20
|
-
start 和 limit 是可以变化的,基于例二,我们自定义字典进行传参,然后进行尝试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
url = 'https://movie.douban.com/j/chart/top_list'
params = { 'type': '5', 'interval_id': '100:90', 'action': '', 'start': '01', 'limit': '20', }
response = requests.get(url=url, params=params, headers=headers)
movie_list = response.json()
for movie in movie_list: print(movie['title'],movie['score'])
|
note:response.json() 可以免除我们手动使用json进行loads的过程。
爬取肯德基的餐厅位置信息
思路:
- 首先,数据是动态加载的,分析请求方式为 post 发出的 url 与 data。
- 通过pageSize就可以在循环内完成多所有地址的爬取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword' data = { 'cname':'', 'keyword':'北京', 'pageIndex':1, 'pageSize':10, 'pid':'' }
with open(data['keyword']+'肯德基地址.txt','w',encoding='utf-8') as f: for i in range(1,data['pageSize']+1): data['pageIndex'] = i address_dic = requests.post(url=url, data=data, headers=headers).json() for address in address_dic['Table1']: print(address['addressDetail']) f.write(address['addressDetail'] + '
')
|
补充:可以使用代理服务器,如搜索:全网代理IP
中标公告提取
提取内容:
工程建设中的中标结果信息/中标候选人信息
-
完整的html中标信息
-
第一中标候选人
-
中标金额
-
中标时间
-
其它参与投标的公司
思路:
- 从首页打开一个公告,先尝试得到一个公告的信息。
- 在得到另一个公告的信息进行比较,只有ID是变化的,所以有了ID就可以批量爬取了。
- 回到首页,判断加载方式,确定数据的请求方式,url以及参数。
实现过程:
- 确认爬取的数据都是动态加载出来的
- 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数
- 对提取到的url进行请求发送,获取响应数据(json),(包含ID信息)
- 从json串中提取到每一个公告对应的id值
- 将id值和中标信息对应的url进行整合,进行请求发送捕获到每一个公告对应的中标信息数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
params = { 'OPtype': 'GetGGInfoPC', 'ID': 132458, 'GGTYPE': 4, 'url': 'AjaxHandler/BuilderHandler.ashx', } headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36', 'Cookie': 'ASP.NET_SessionId=j0ps3kcjisaajyenlgihwwxo; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570523570; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570523621; _qddagsx_02095bad0b=115b25431c8f1a2f7f607e8464ba7c5ef5807a77e65a44aa3c9045306ab0ba3bf02c48523e97b816f16d9ff0c57b6e77f46e59f8776b88c64cbd9da7f84676d8c4c9db3686235ef49e9ee7ff1871ec99884e7ba79b8c173e472b039b0c9a8fb61b4049ab036f68e1f5e0857c4bd4131f0c3b7478b98687d0b9c0538352871ec9', } url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx' response = requests.get(url=url, params=params, headers=headers) response.text
|
完成了对公告详情的爬取后,接下来批量爬取公告。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx' headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36', 'Cookie': 'ASP.NET_SessionId=j0ps3kcjisaajyenlgihwwxo; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570523570; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570523621; _qddagsx_02095bad0b=115b25431c8f1a2f7f607e8464ba7c5ef5807a77e65a44aa3c9045306ab0ba3bf02c48523e97b816f16d9ff0c57b6e77f46e59f8776b88c64cbd9da7f84676d8c4c9db3686235ef49e9ee7ff1871ec99884e7ba79b8c173e472b039b0c9a8fb61b4049ab036f68e1f5e0857c4bd4131f0c3b7478b98687d0b9c0538352871ec9', } 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.8853761868569314, } params = { 'OPtype': 'GetGGInfoPC', 'ID': 132458, 'GGTYPE': 4, 'url': 'AjaxHandler/BuilderHandler.ashx', } count = 0 for i in range(1,6): data['pageNo'] = i response = requests.post(url=url,data=data,headers=headers) post_data = response.json() for row in post_data['data']: ID = int(row['M_ID']) params['ID'] = ID company_respose = requests.get(url=url, params=params, headers=headers) company_detail = company_respose.json()['data']
count += 1 print(count)
|
爬取图片
如何做?
- 基于requests
- 基于urllib
- 区别:urllib中的 urlretrieve 不可以进行UA伪装
requests在urllib基础上产生,更加pythonic!
基于requests的图片爬取
1 2 3 4 5 6 7 8 9
|
import requests headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36' }
url = r'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1570593174006&di=5ade9e1cb9e63a708c69095283f8e40a&imgtype=0&src=http%3A%2F%2Fdingyue.nosdn.127.net%2F92Ot1vmaeOklEbu2G7ABakMeGiYWpYi8R3urPnggBDJSs1535663928397.jpeg' img_data = requests.get(url=url, headers=headers).content with open('./123.jpg','wb') as fp: fp.write(img_data)
|
- request更具通用性,数据可以展示为:text(字符串),json(列表/字典),content(字节)。
基于urllib的图片爬取
1 2 3 4
|
from urllib import request url = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1570593174006&di=5ade9e1cb9e63a708c69095283f8e40a&imgtype=0&src=http%3A%2F%2Fdingyue.nosdn.127.net%2F92Ot1vmaeOklEbu2G7ABakMeGiYWpYi8R3urPnggBDJSs1535663928397.jpeg' request.urlretrieve(url,'./456.jpg')
|
- 由于urlretrieve不能做UA伪装,所以存在图片缺失的可能。
聚焦爬虫
较通用爬虫相比,增加了数据解析。
数据解析
概念:将一整张页面的局部数据进行提取/解析
作用:用来实现聚焦爬虫
实现方式:
数据解析的通用原理是什么?
页面中的相关的字符串的数据都存储在哪里?
基于聚焦爬虫的编码流程
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 持久化存储
正则解析
爬取煎蛋网中的图片
- 地址为:
http://jandan.net/pic/MjAxOTEwMDktNjk=#comments
实现过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
import re import os url = 'http://jandan.net/pic/MjAxOTEwMDktNjk=#comments' page_text = requests.get(url=url,headers=headers).text
ex = r'<div class="text">.*?<img src="(.*?)" referrerPolicy'
dirName = './JDimg/' if not os.path.exists(dirName): os.mkdir(dirName) img_url_list = re.findall(ex,page_text,re.S) for img_url in img_url_list: ex2 = r'org_src="(.*?)"' gif_url = re.findall(ex2,img_url) if gif_url: new_url = 'http:' + gif_url[0] else: new_url = 'http:' + img_url name = new_url.split('/')[-1] img = requests.get(new_url,headers=headers).content img_path = dirName + name with open(img_path,'wb') as fp: fp.write(img) print(name,'下载完成!!!')
|
note:内容依据响应数据page_text,而不是根据浏览器中网页上的代码,上面就出现一个字母的大小写不同而导致正则失效的例子。
bs4解析
环境的安装:
- pip install bs4
- pip install lxml
bs4的解析原理:
- 实例化一个BeatifulSoup的一个对象,把即将被解析的页面源码数据加载到该对象中
- 需要调用BeatifulSoup对象中的相关的方法和属性进行标签定位和数据的提取
BeatifulSoup的实例化
- BeatifulSoup(fp, ‘lxml’) 将本地存储的html文档中的页面源码数据加载到该对象中
- BeatifulSoup(page_text, ‘lxml’) 将从互联网中请求到的页面源码数据加载到该对象中
标签的定位
熟悉
此为练手的html。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
<html lang="en"> <head> <meta charset="UTF-8" /> <title>测试bs4</title> </head> <body> <div> <p>百里守约</p> </div> <div class="song"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href="http://www.song.com/" title="赵匡胤" target="_self"> <span>this is span</span> 宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a> <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a> <img src="http://www.baidu.com/meinv.jpg" alt="" /> </div> <div class="tang"> <ul> <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li> <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li> <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li> <li><a href="http://www.sina.com" class="du">杜甫</a></li> <li><a href="http://www.dudu.com" class="du">杜牧</a></li> <li><b>杜小月</b></li> <li><i>度蜜月</i></li> <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li> </ul> </div> </body> </html>
|
熟悉 BeautifulSoup 的选择器以及如何取文本和属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
fp = open('./test.html',encoding='utf-8') soup = BeautifulSoup(fp ,'lxml') soup.div type(soup.div) soup.find('div') soup.find('div',class_="song")
soup.findAll('div') soup.select('div') soup.select('div[class="song"]') soup.select('.song') soup.select('div > ul > li') soup.select('div li')
soup.select('div li')[0].text soup.select('.song')[0].text soup.select('.song')[0].string soup.select('b')[0].string
soup.select('div a')[0]['href']
|
爬取三国演义小说的标题和内容
- 地址为:
http://www.shicimingju.com/book/sanguoyanyi.html
数据解析流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html' page_text = requests.get(url=main_url,headers=headers).text
soup = BeautifulSoup(page_text,'lxml') li = soup.select('.book-mulu a') fp = open('./sanguo.txt','w',encoding='utf-8') for i in li: title = i.string detail_url = 'http://www.shicimingju.com' + i['href'] detail_page_text = requests.get(url=detail_url,headers=headers).text detail_soup = BeautifulSoup(detail_page_text,'lxml') detail = detail_soup.find('div',class_='chapter_content').text fp.write(title + '
' + detail + '
') fp.close()
|
xpath 解析
-
环境的安装
-
解析原理
- 实例化一个etree的对象,且把即将被解析的页面源码数据加载到该对象中
- 调用etree对象中的xpath方法结合着不同形式的xpath表达式进行标签定位和数据提取
-
etree对象的实例化
- etree.parse(‘fileName’)
- etree.HTML(page_text)
-
标签定位
-
取文本
- /text():取直系的文本内容
- //text():取所有文本内容
-
取属性
note:
- 返回的都是列表
- 在google中的Element可以直接copy xpath
熟悉
依旧使用上面的html练手
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
from lxml import etree tree = etree.parse('./test.html') tree tree.xpath('/html') tree.xpath('/html//title') tree.xpath('//div')
tree.xpath('//*[@class="tang"]') tree.xpath('//div[@class="song"]') tree.xpath('//div[@class="tang"]/ul/li')
tree.xpath('//div[@class="tang"]/ul/li[2]')
tree.xpath('//b/text()') tree.xpath('//div[@class="tang"]/ul/li//text()')
tree.xpath('//a/@href')
|
爬取虎牙主播名称,热度和标题
1 2 3 4 5 6 7 8 9 10
|
url = 'https://www.huya.com/g/xingxiu' page_text = requests.get(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]/@title')[0] name = li.xpath('./span/span[1]/i/@title')[0] hot = li.xpath('./span/span[2]/i[2]/text()')[0] print(name,'的直播间: ',title,'热度为:',hot)
|
爬取所有页码的妹子
地址为:http://pic.netbian.com/4kmeinv/
- 涉及中文乱码
- 以前对response的 encoding 这样代价大
iso-8859-1 –> utf-8 先编码成iso-8859-1 在解码成 utf或gbk
- 多页码数据的爬取