Python爬虫
环境配置
1 | conda create --name spider python=3.8 |
第一个爬虫程序
- Python urllib 库为Python的自带库,用于操作网页 URL,并对网页的内容进行抓取处理。
1 | from urllib.request import urlopen |
- 保存到html文件中
1 | from urllib.request import urlopen |
Web请求过程
-
服务器渲染:在服务器那边直接把数据和html整合在一起,统一返回给浏览器
在页面原代码中可以看到数据
-
客户端渲染:第一次请求只要一个html框架,第二次请求拿到数据进行数据展示
在页面原代码中看不到数据
在豆瓣/电影/排行榜/喜剧中,查看网页原代码,里面是没有数据的,虽然网页上显示了憨豆先生,但在原代码里面查找不到。这种渲染是客户端渲染。
熟练使用浏览器抓包工具
在网页界面按F12
打开开发者工具,点击网络(network),刷新一次页面,在网络下面可以看到网页渲染的全部请求,这些请求里面包括 对数据的请求。
HTTP协议
协议就是两个计算机之间为了能够流畅地进行沟通而设置的一个君子协定。常见的协议有TCP/IP,SOAP协议,HTTP协议 ,SMTP协议等等
HTTP协议,即超文本传输协议(Hyper Text Transfer Protocol,HTTP),是一个简单的请求-响应协议。它是从万维网(WWW——World Wide Web)服务器传输超文本到本地浏览器的传送协议。直白点就是浏览器和服务器之间的传输网页原代码需要遵守的协议。
HTTP协议把一条消息分为三大块内容,无论是请求还是响应都是三块内容
- 请求
1 | 请求行 -> 请求方式 请求url地址 协议 |
- 响应
1 | 状态行 -> 协议 状态码(200,404,500,302等) |
请求标头中常见的一些重要内容(爬虫需要):
- User-Agent:请求载体的身份标识(用什么发送的请求)
- Referer:防盗链(这次请求时从哪个页面来的?反爬会用)
- Cookie:本地字符串数据信息(用户登录信息,反爬的token)
响应标头中的一些重要的内容:
- Cookie:本地字符串数据信息(用户登录信息,反爬的token)
- 各种神奇的莫名其妙的字符串(这个需要经验,一般都是token字样,防止各种攻击和反爬)
请求方式:
- GET:显式的提交
- POST:隐式的提交
Request库入门
Request库并不是Python自带的模块
Request库的安装
1 | pip install requsets |
案例1——搜狗周杰伦
-
在搜狗中搜索周杰伦,获得周杰伦的url链接
注意:直接输入“周杰伦”回车得到的长串链接只需要保留
...query=周杰伦
即可,后面的部分可以删除
1 | import requests |
- 返回的信息中提示需要验证
1 | ... |
-
右键检查或者F12进入调试界面
在请求头中获取
User-Agent
,它是用来描述当前的请求是通过什么设备发出的
1 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 |
-
在爬虫程序中加一个标头
给
User-Agent
加上一个双引号,以及冒号后面的内容也加上一个双引号python1
2
3
4
5
6
7
8
9import requests
url = 'https://www.sogou.com/web?query=周杰伦'
request_header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
resp = requests.get(url, headers=request_header)
print(resp.text)
案例二——百度翻译
-
进入百度翻译的网页并打开调试界面
点开网络,清除无关的请求
使用英文输入法输入
dog
,出现4个下拉菜单在网络的请求中找到
sug
,点开预览,可以看到4个下拉菜单的内容
-
在
Headers->General
中可以获得url传入的参数
dog
被保存在字段kw
中
-
这里的传入参数并不像上一个周杰伦的例子,保存在url中,而是在data里面
python1
2
3
4
5
6
7
8
9
10
11
12
13import requests
url = "https://fanyi.baidu.com/sug"
## 这里的传入参数并不像上一个周杰伦的例子,保存在url中,而是在data里面
s = input("请输入你要翻译的英文单词: ")
keyword = {
"kw" : s
}
## 发送post请求,发送的数据必须放在字典中,通过data参数进行传递
resp = requests.post(url, data = keyword)
## 将服务器返回的内容直接处理成json() => dict
print(resp.json())
案例三——豆瓣电影
- 进入网页豆瓣->电影->排行榜->分类排行榜->喜剧
- 获得对应内容的url
-
这个url中,问号
?
前面的部分是链接,后面的部分是参数html1
Request URL: https://movie.douban.com/j/chart/top_list?type=24&interval_id=100%3A90&action=&start=0&limit=20
-
对应的参数在payload中
-
通过
Request Method: GET
可以知道它的请求方式是GET
-
重新封装参数
python1
2
3
4
5
6
7
8
9
10
11
12import requests
## 去掉?后的参数部分,将其封装在param中
url = "https://movie.douban.com/j/chart/top_list"
param = {
"type": "24",
"interval_id": "100:90",
"action": "",
"start": 0,
"limit": 20,
}
resp = requests.get(url = url, params = param)
print(resp.url)python1
2## 返回的链接和获取的Request URL一样
https://movie.douban.com/j/chart/top_list?type=24&interval_id=100%3A90&action=&start=0&limit=20 -
直接
print(resp.url)
什么都不返回,原因是被反爬了 -
先尝试修改
User-Agent
- 输出
headers
,查看Python默认的User-Agent
是什么
python1
print(resp.request.headers)
- 可以看到Python默认的
User-Agent
是'python-requests/2.28.1'
python1
{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
-
去浏览器调试界面
Request Headers
下找到User-Agent
,将其添加到爬虫代码中python1
2
3headers = {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
- 输出
-
最后代码
python1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import requests
url = "https://movie.douban.com/j/chart/top_list"
## 重新封装参数
param = {
"type": 24,
"interval_id": "100:90",
"action": "",
"start": 0,
"limit": 20,
}
## 设置headers,浏览器标识UA
headers = {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
resp = requests.get(url = url, params = param, headers = headers)
print(resp.json())
resp.close() -
在访问完服务器之后记得关闭访问,如果不关则会和服务器一直保持连接,同时访问多个服务器可能会报错
python1
resp.close() ## 关掉resp
-
参数
start
的值每20每20地往上加,修改参数start
的值可以爬取其它的内容
Re解析
正则表达式
Regular Expression
-
常用元字符:
python1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17. 匹配除换行符以为的任意字符
\w 匹配字母数字下划线
\s 匹配任意空白符
\d 匹配数字
\n 匹配一个换行符
\t 匹配一个制表符
^ 匹配字符串的开头
$ 匹配字符串的结尾
\W 匹配非字母数字下划线
\D 匹配非数字
\S 匹配非空白符
| 逻辑或
() 表示组
[...] 匹配[]中的所有字符
[^...] 匹配除了[^]中的所有字符 -
量词:
python1
2
3
4
5
6* 重复零次或者更多次
+ 重复一次或者更多次
? 重复零次或者一次
{n} 重复n次
{n,} 重复n次或者更多次
{m,n}重复n次到m次 -
贪婪匹配和惰性匹配
python1
2.* 贪婪匹配——匹配尽可能长的字符串
.*? 惰性匹配——匹配尽可能短的字符串
正则表达式快速替换
- 从网页上复制粘贴来的参数是这样,需要给它们加上引号和逗号
1 | param = { |
- 可以使用正则表达式快速替换
1 | 匹配:(\w+?): (\w+) |
- 替换后结果如下
1 | param = { |
re模块
-
导入模块
python1
import re
-
findall
findall返回一个列表
python1
2lst = re.findall(r"\d+", "移动客服10086,电信客服10000")
print(lst, type(lst))python1
2## 返回一个列表
['10086', '10000'] <class 'list'> -
finditer
finditer返回一个迭代器,从迭代器中拿数据需要用
.group()
python1
2itr = re.finditer(r"\d+", "移动客服10086,电信客服10000")
print(itr, type(itr))python1
2## 返回一个迭代器
<callable_iterator object at 0x000001C373DF2B20> <class 'callable_iterator'>python1
2
3
4
5for i in iter:
print(i.group())
## 输出
10086
10000 -
search
search返回的结果是match对象,拿数据需要
.group()
search全文匹配,并且只返回找到的第一个结果
python1
2sch = re.search(r"\d+", "移动客服10086,电信客服10000")
print(sch.group())python1
10086
-
match
match是从头开始匹配的,默认在正则前加了一个
^
,不是常用方法python1
2
3
4mch = re.match(r"\d+", "移动客服10086,电信客服10000")
print(mch.group())
## 输出会报错,因为mch并没有匹配到 -
预加载正则表达式
预加载正则表达式可以稍稍提高一点程序运行的效率,并且可以多次使用
python1
2
3
4obj = re.compile(r"\d+")
lst = obj.findall(r"\d+", "移动客服10086,电信客服10000")
itr = obj.finditer(r"\d+", "移动客服10086,电信客服10000") -
re.S
让.
能匹配换行符python1
obj = re.compile(r"xxx", re.S)
-
使用组
使用
?P<group_name>
可以给组起名python1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import re
s = """
移动客服:10086;
电信客服:10000;
联通客服:10010。
"""
obj = re.compile(r"^(?P<Name>.*客服).(?P<Number>\d{5}).$",re.M)
itr = obj.finditer(s)
for i in itr:
print(i.group("Name"))
print(i.group("Number"))python1
2
3
4
5
6
7## 输出
移动客服
10086
电信客服
10000
联通客服
10010
实战——豆瓣Top250
https://movie.douban.com/top250
- 可以先打开源代码,查看源代码中有没有我们想要爬取的内容
- 豆瓣Top205的原代码中已经体现了电影的排名、名称和评分等信息
- 那么爬取这个网站的步骤就是1.通过requests模块先拿到网页源代码2.再通过re模块提取有效信息
获得网页源代码
- 通过requests模块获取网页源代码
1 | import requests |
-
奇怪的地方是直接在终端中输出
resp.text
并没有显示完整的html源代码,但将其保存到本地文件中却是完整的代码python1
print(resp.text) ## 并不输出完整代码
匹配关键信息
- 先观察源代码,观察需要匹配信息的特征
-
编写正则表达式
正则表达式在线验证工具:https://regex101.com/
可以在网站上验证自己的正则表达式是否正确的
注意要设置单行匹配,否则
.
不能匹配换行符python1
<li>.*?<em class="">(?P<ranking>\d+).*?<span class="title">(?P<title>.*?)<\/span>.*?<span class="rating_num" property="v:average">(?P<score>[\d\.]+)<\/span>.*?(?P<Number>\d+)人评价'
-
得到目标信息
python1
2
3obj = re.compile(r'<li>.*?<em class="">(?P<ranking>\d+).*?<span class="title">(?P<title>.*?)<\/span>.*?<span class="rating_num" property="v:average">(?P<score>[\d\.]+)<\/span>.*?(?P<number>\d+)人评价', re.S)
itr = obj.finditer(resp.text) -
输出目标信息
python1
2
3
4
5
6for i in itr:
print("ranking : " + i.group("ranking"))
print("title : " + i.group("title"))
print("score : " + i.group("score"))
print("number : " + i.group("number"))
print("---------\n")python1
2
3
4
5
6
7
8
9
10
11
12ranking : 1
title : 肖申克的救赎
score : 9.7
number : 2659667
---------
...
ranking : 25
title : 怦然心动
score : 9.1
number : 1694301
--------- -
整个代码
python1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import requests
import re
url = "https://movie.douban.com/top250"
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
resp = requests.get(url, headers = header)
obj = re.compile(r'<li>.*?<em class="">(?P<ranking>\d+).*?<span class="title">(?P<title>.*?)<\/span>.*?<span class="rating_num" property="v:average">(?P<score>[\d\.]+)<\/span>.*?(?P<number>\d+)人评价', re.S)
itr = obj.finditer(resp.text)
for i in itr:
print("ranking : " + i.group("ranking"))
print("title : " + i.group("title"))
print("score : " + i.group("score"))
print("number : " + i.group("number"))
print("---------\n")
将数据存入csv文件
1 | import csv |
1 | 1,肖申克的救赎,9.7,2659674 |
修改参数
- 不同页数url对应的参数不一样,修改这个参数可以爬取不同页面的数据
- 第一页的参数是
start=0
,第二页的参数是start=25
,以此类推