基于request的爬虫练习

引言

概述

概念:基于网络请求的模块

作用:用来模拟浏览器发请求,从而实现爬虫

通用爬虫

步骤:

  1. 指定url
  2. 请求发送:get返回的是一个响应对象
  3. 获取响应数据: text返回的是字符串形式的响应数据
  4. 持久化存储

爬取搜狗首页的页面源码数据

1
2
3
4
5
6
7
8
9
10
11
import requests
# 1.指定url
url = 'https://www.sogou.com/'
# 2.请求发送:get返回的是一个响应对象
response = requests.get(url=url)
# 3. 获取响应数据: text返回的是字符串形式的响应数据
page_text = response.text
page_text
# 4. 持久化存储
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
}
# 没有做UA伪装是得不到数据的
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',
}
# start 决定起始位置,limit 为显示数量。
response = requests.get(url=url, params=params, headers=headers)

# json() 返回序列化好的对象(字典,列表)
movie_list = response.json()
# 手动反序列化
# import json
# movie_list = json.loads(movie_list)
for movie in movie_list:
print(movie['title'],movie['score'])

note:response.json() 可以免除我们手动使用json进行loads的过程。

爬取肯德基的餐厅位置信息

  • 地址为:http://www.kfc.com.cn/kfccda/storelist/index.aspx

  • post请求

  • 一次爬取多页数据

思路:

  • 首先,数据是动态加载的,分析请求方式为 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'] + '\n')

补充:可以使用代理服务器,如搜索:全网代理IP

中标公告提取

提取内容:

工程建设中的中标结果信息/中标候选人信息

  1. 完整的html中标信息

  2. 第一中标候选人

  3. 中标金额

  4. 中标时间

  5. 其它参与投标的公司

思路:

  1. 从首页打开一个公告,先尝试得到一个公告的信息。
  2. 在得到另一个公告的信息进行比较,只有ID是变化的,所以有了ID就可以批量爬取了。
  3. 回到首页,判断加载方式,确定数据的请求方式,url以及参数。

实现过程:

  • 确认爬取的数据都是动态加载出来的
  • 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数
  • 对提取到的url进行请求发送,获取响应数据(json),(包含ID信息)
  • 从json串中提取到每一个公告对应的id值
  • 将id值和中标信息对应的url进行整合,进行请求发送捕获到每一个公告对应的中标信息数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 该部分为得到一个公告的信息,但是我们这里的ID不灵活,需要进一步改进。
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
# 该部分完成了对前5页的公告信息进行爬取
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
# 第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']
# print(company_detail)
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'
}
# 基于requests的图片爬取
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 # content 返回的是bytes类型的响应数据
with open('./123.jpg','wb') as fp:
fp.write(img_data)
  • request更具通用性,数据可以展示为:text(字符串),json(列表/字典),content(字节)。

基于urllib的图片爬取

1
2
3
4
# 基于urllib
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伪装,所以存在图片缺失的可能。

聚焦爬虫

较通用爬虫相比,增加了数据解析

数据解析

概念:将一整张页面的局部数据进行提取/解析

作用:用来实现聚焦爬虫

实现方式:

  • 正则
  • bs4
  • xpath
  • pyquery

数据解析的通用原理是什么?

  • 标签的定位
  • 数据的提取

页面中的相关的字符串的数据都存储在哪里?

  • 标签中间
  • 标签的属性中

基于聚焦爬虫的编码流程

  • 指定url
  • 发起请求
  • 获取响应数据
  • 数据解析
  • 持久化存储

正则解析

爬取煎蛋网中的图片

  • 地址为:http://jandan.net/pic/MjAxOTEwMDktNjk=#comments

实现过程:

  • 指定url
  • 获取响应数据
  • 数据解析
    • 写正则表达式
    • 正则匹配
  • 持久化存储
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
# 解析数据:img标签的src的属性值

# 写正则表达式
# print(page_text) # 参考page_text来定正则,网页源码可能有些小问题
# ex = r'<div class="text">.*?<img src="(.*?)" referrerpolicy' # 坑!!!
ex = r'<div class="text">.*?<img src="(.*?)" referrerPolicy' # 在源码中那里的p是小写,可爬下来的p是大写,坑~~~

dirName = './JDimg/'
if not os.path.exists(dirName):
os.mkdir(dirName)
img_url_list = re.findall(ex,page_text,re.S) # 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’) 将从互联网中请求到的页面源码数据加载到该对象中

标签的定位

  • soup.tagName: 只可以定位到第一个tagName标签

  • 属性定位:

    • soup.find(‘div’,’attrName=’value’) 只能定位到符合要求的第一个
    • soup.findAll:返回列表,可以定位到符合要求的所有标签
      note:只有class需要加下划线,其它直接用原名就可以。
  • 选择器定位:

    • select(‘选择器’)
    • 选择器:id,class,tag,层级选择器(大于号表示一个层级,空格表示多个层级)
  • 取文本

    • text:将标签中所有文本取出
    • string:将标签中直系的文本取出
  • 取属性

    • tag[‘attrName’]

熟悉

此为练手的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) # bs4.element.Tag
soup.find('div')
soup.find('div',class_="song") # 由于class 是关键字,所以需要加下划线

#下面的 findAll 与 select 都将返回列表
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
# 数据解析: 章节的标题和详情页的url
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 + '\n' + detail + '\n\n')
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

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 # <lxml.etree._ElementTree at 0x231b25371c8>
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')
# print(page_text)
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
  • 多页码数据的爬取
    • 制定一个通用的url模板

猜你喜欢

转载自www.cnblogs.com/taosiyu/p/11645901.html
今日推荐