requests
GET 请求
import json
import requests
params = {
'age': 25,
'name': 'John'
}
response = requests.get(
url='https://www.httpbin.org/get',
params=params, # GET请求传参
)
print(type(response))
print(type(response.text))
# 调用json()方法,可以将JSON格式的返回结果转化为字典
# 如果返回结果不是JSON格式,会抛出解析错误JSON.decoder.JSONDecodeEorror
print(type(response.json()))
print(response.text)
抓取网页
import requests
import re
response = requests.get('https://ssr1.scrape.center/')
# 正则表达式
pattern = re.compile('<h2.*?>(.*?)</h2>', re.S)
titles = re.findall(pattern, response.text)
print(titles)
抓取二进制数据(图片、音频、视频等)
import requests
response = requests.get('https://scrape.center/favicon.ico')
# 将结果转化成bytes类型数据
print(response.content)
with open('favicon.ico', 'wb') as f:
f.write(response.content)
最前面的b表示这是bytes类型数据。
添加请求头
没有设置请求头,会被某些网站发现这并不是由正常浏览器发起的请求,导致返回结果异常。
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Machintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko)'
'Chrome/52.0.2743.116 Safari/ 537.36'
}
response = requests.get('https://ssr1.scrape.center/', headers=headers)
print(response.text)
POST请求
import requests
data = {
'name': 'John',
'age': 25
}
response = requests.post("https://www.httpbin.org/post", data=data)
print(response.text)
返回结果中的form部分就是提交的数据,证明POST请求发送成功。
响应
text和content可以获取响应内容,除此之外还有其他属性和方法用来获取响应的其他信息。
import requests
response = requests.get("https://ssr1.scrape.center/")
print(type(response.status_code), response.status_code) # 获取响应的状态码
print(type(response.headers), response.headers) # 获取响应头
print(type(response.cookies), response.cookies) # 获取Cookies
print(type(response.url), response.url) # 获取URL
print(type(response.history), response.history) # 获取请求历史
requests库提供内置状态码查询对象requests.codes
import requests
response = requests.get("https://ssr1.scrape.center/")
print("Request Failed") if response.status_code != requests.codes.ok else print("Request Successfully")
这里通过比较返回的状态码和内置的表示成功的状态码,来保证请求是否得到了正常响应。这样就不需要再在程序里写状态码对应的数字了,用字符串表示状态码会更加直观。
高级用法
文件上传
import requests
files = {'file': open('favicon.ico', 'rb')}
response = requests.post("https://www.httpbin.org/post", files=files)
print(response.text)
Cookie设置
response = requests.get("https://www.baidu.com")
print(response.cookies)
for cookie in response.cookies:
print(type(cookie))
print(cookie.__dict__)
print(cookie.name+"="+cookie.value)
# items()方法可以将Cookies转化成元组列表
for key, value in response.cookies.items():
print(key+"="+value)
也可以直接用Cookie来维持登录状态。将百度的Cookie复制下来,添加在请求的headers中:
import requests
headers = {
'Cookie': "...",
'User-Agent': '...'
}
response = requests.get("https://www.baidu.com", headers=headers)
print(response.text)
结果会包含很多登录后才可见的内容,这样就可以成功模拟登录状态。爬取登录之后才可见的页面。
也可以通过cookies参数来设置Cookie信息。
import requests
cookies = "..."
# 构造一个RequestsCookieJar对象
jar = requests.cookies.RequestsCookieJar()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'
}
for cookie in cookies.split(";"):
# 对Cookies进行处理和赋值
key, value = cookies.split("=", 1)
jar.set(key, value)
response = requests.get("https://www.baidu.com", headers=headers, cookies=jar)
print(response.text)
Session 维持
维持同一个Session,这样多次访问同一个网站,就不算各自独立的访问。
import requests
# 设置一个Cookie条目,名称是number,内容是123456789
requests.get("https://www.httpbin.org/cookies/set/number/123456789")
# 发送请求以获取当前的Cookie信息
response = requests.get("https://www.httpbin.org/cookies")
print(response.text)
发现并不能获取Cookie。
import requests
session = requests.Session()
session.get("https://www.httpbin.org/cookies/set/number/123456789")
response = session.get("https://www.httpbin.org/cookies")
print(response.text)
利用Session可以做到模拟同一个会话,而不用担心Cookie问题。Session通常在模拟登录之后,进行下一步操作时用到。
SSL证书验证
并不是所有网站都设置好了HTTPS证书,访问这些网站可能会出现SSL证书错误的提示。如果希望用requests库访问这些网站,需要将控制是否验证证书的参数verify设置为False。
import requests
response = requests.get("https://ssr2.scrape.center/", verify=False)
print(response.status_code)
虽然响应的状态码是200,但是还是收到了一个警告。
超时设置
timeout参数设置从发出请求到服务器返回响应的时间。超过这个时间会报错。
import requests
response = requests.get("https://www.httpbin.org/get", timeout=1) # 超时时间单位为秒
print(response.status_code)
实际上请求分两个阶段:连接(connect)和读取(read)。上面的例子中设置的timeout是连接和读取时间的总和,如果要分开设置,则传入一个元组。
import requests
response = requests.get("https://www.httpbin.org/get", timeout=(5, 10))
print(response.status_code)
如果希望永久等待,可以将timeout设置为None,或者省缺,因为timeout默认取值为None。这样永远不会返回超时错误。
身份认证
用auth参数设置用户名和密码信息
import requests
response = requests.get("https://www.httpbin.org/get", auth=('admin', 'admin'))
print(response.status_code)
这里用户名和密码都是admin。
代理设置
大规模爬取会产生大量频繁的请求,要被爬取的网站就可能弹出验证码,或者跳转到登录认证页面。为了防止这种问题,我们需要代理来解决这个问题。
import requests
proxies = {
'http': 'http://10.10.10.10:1080',
'https': 'http://10.10.10.10:1080'
}
requests.get("http://www.httpbin.org/get", proxies=proxies)
如果需要身份验证,可以使用类似http://user:password@host:port这样的语法来设置代理。
import requests
proxies = {
'https': 'http://user:password@10.10.10.10:1080',
}
requests.get("http://www.httpbin.org/get", proxies=proxies)
Prepared Request
请求在requests库内部是怎么实现的呢?实际上,requests库在发送请求的时候,在内部构造了一个Request对象,并给这个对象赋值了各种参数,包括url, headers, data等,然后直接把这个Request对象发送出去。请求成功之后再得到一个Response对象,解析这个对象即可。
from requests import Request, Session
url = "https://www.httpbin.org/post"
data = {'name': 'Mack'}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'
}
# 构造了一个Request对象
request = Request('POST', url, data=data, headers=headers)
session = Session()
# 使用Session类的prepare_request()方法将其转换成一个Prepared Request对象
prepped = session.prepare_request(request)
# 调用send()方法发送Prepared Request对象
response = session.send(prepped)
print(response.text)
从结果看,我们达到了POST请求的效果。有了Request对象,就可以将请求当作独立的对象来对待,能更灵活地实现请求调度和各种操作。
正则表达式库re
request库可以获取网页的源代码,得到HTML代码。数据则是在HTML中的,正则表达式可以实现字符串的检索、替换、匹配、验证,可以从HTML中提取想要的信息。
常用匹配规则
| 模式 | 匹配 | |
|---|---|---|
| \w | 字母 数字 下划线 | |
| \W | 不是字母、数字和下划线的字符 | |
| \s | 任意空白字符,等价于[\t\n\r\f] | |
| \S | 任意非空白字符 | |
| \d | 任意数字,等价于[0-9] | |
| \D | 任意非数字字符 | |
| \A | 字符串开头 | |
| \Z | 字符串结尾,如果存在换行,只匹配到换行前的结束字符串 | |
| \z | 字符串结尾,如果存在换行,还会匹配换行符 | |
| \G | 最后匹配完成的位置 | |
| \n | 一个换行符 | |
| \t | 一个制表符 | |
| 一行字符串的开头 | ||
| $ | 一行字符串的结尾 | |
| . | 任意字符,除了换行符。当re.DOTALL标记被指定时,可以必备包括换行符在内的任意字符 | |
| [...] | 用来表示一组字符,单独列出,例如[amk]用来匹配a, m或者k | |
| [^...] | 匹配不在[]中的字符串,例如[^amk]用来匹配a, m或者k以外的字符 | |
| * | 0个或者多个表达式 | |
| + | 1个或者多个表达式 | |
| ? | 0个或者1个前面的正则表达式片段,非贪婪方式 | |
| {n} | 精准匹配n个前面的表达式 | |
| {n, m} | n到m次由前面正则表达式定义的片段,贪婪方式 | |
| a | b | a到b |
| () | 括号内的一个表达式,也表示一个组 |
match
import re
content = "Hello 123 4567 World_This is a Regex Demo"
print(len(content))
result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}', content)
print(result)
# group()方法输出匹配到的内容
print(result.group())
# span()方法输出匹配到的范围
print(result.span())
正则表达式'^Hello\s\d\d\d\s\d{4}\s\w{10}'用来匹配content字符串。
- ^表示匹配字符串的开头,这里就是以Hello开头。
- \s表示匹配空白字符,用来匹配Hello之后的空格。
- \d表示匹配数字,\d\d\d匹配123
- \d{4}表示表示数字匹配4次,匹配4567
- \w{10}表示匹配10次数字、字母和下划线
match方法中,第一个参数是正则表达式,第二个参数是要匹配的字符串。
可以看到,result是re库的Match类对象。证明匹配成功。
匹配目标
import re
content = "Hello 1234567 World_This is a Regex Demo"
print(len(content))
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())
()可以将想要提取的子字符串括起来,被标记的每个子表达式依次对用每个分组,调用group()方法时候传入分组的索引就能提取结果。
import re
content = "Hello 1234567 World_This is a Regex Demo"
print(len(content))
result = re.match('^Hello\s(\d+)\s(\w+)', content)
print(result)
print(result.group())
print(result.group(1))
print(result.group(2))
print(result.span())
这回World_This也被匹配出来了
通用匹配
.可以匹配任意字符(除换行符),*可以表示匹配前面的字符无限次,.*就能表示匹配任意字符了。
import re
content = "Hello 123 4567 World_This is a Regex Demo"
result = re.match('Hello.*Demo$', content)
print(result)
print(result.group())
print(result.span())
贪婪与非贪婪
import re
content = "Hello 1234567 World_This is a Regex Demo"
result = re.match('He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))
group(1)匹配道德只有数字7,并没有比配上全部数字。
在贪婪匹配下,.*会尝试匹配尽可能多的字符。(\d+)至少一位数字,那么.*则匹配了'llo 123456',只给\d+留下了一个可满足条件的数字7。
import re
content = "Hello 1234567 World_This is a Regex Demo"
result = re.match('He.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))
.*?可以转化为非贪婪匹配。.*?会匹配尽可能少的字符。
修饰符
import re
content = "Hello 1234567 World_This \n is a Regex Demo"
result = re.match("He.*?(\d+).*?Demo", content)
print(result)
添加了换行符之后,就没法匹配了。说明.*?无法匹配换行符。这里需要添加一个修饰符re.S。
import re
content = "Hello 1234567 World_This \n is a Regex Demo"
result = re.match("He.*?(\d+).*?Demo", content, re.S)
print(result)
print(result.group(1))
re.S修饰符的作用是使匹配内容包括换行符在内的所有字符。常见修饰符如下:
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 实现本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响^和$ |
| re.S | 使匹配内容包括换行符在内的所有字符 |
| re.U | 根据Unicode字符集解析字符。这个标志会影响\w, \W, \b和\B |
| re.X | 该标志能够给予你更灵活的格式,以便将正则表达式书写的更易于理解 |
转义匹配
import re
content = "(百度)www.baidu.com"
result = re.match('\(百度\)www\.baidu\.com', content)
print(result.group())
这样就能匹配特殊字符,例如.和(。
search
match()方法是从字符串的开头开始匹配,意味着一旦开头不匹配,则整个匹配就失败了。
import re
content = "Extra stings Hello 1234567 World_This is a Regex Demo Extra stings"
result = re.match('Hello.*?(\d+).*?Demo', content)
print(result)
print(result.group(1))
字符串以Extra开头,但是正则表达式以Hello开头,所以匹配失败了。
换成search()方法:
import re
content = "Extra stings Hello 1234567 World_This is a Regex Demo Extra stings"
result = re.search('Hello.*?(\d+).*?Demo', content)
print(result)
print(result.group(1))
print(result.span(1))
获取一个视频网站的HTML,然后找出第一个热门视频的标题。
import re
import requests
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
response = requests.get('https://www.bilibili.com/v/dance/otaku/?spm_id_from=333.1073.0.0', headers=headers)
result = re.search('<span>热门.*?title=.*?>(.+?)</h3>', response.text, re.S)
print(result.group(1))
这样会返回第一个匹配结果。
findall
search()方法只能匹配第一个与正则表达式相符的字符串。findall()可以匹配全部符合的正则表达式:
import re
import requests
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
response = requests.get('https://www.bilibili.com/v/dance/otaku/?spm_id_from=333.1073.0.0', headers=headers)
results = re.findall('<h3 class.*?title=.*?>([^<]+?)</h3>', response.text, re.S)
print(type(results))
for result in results:
print(result)
可以看到findall的返回结果是一个list。如果匹配多个子字符串,返回的每一项是一个元组,可以通过下标访问具体的子字符串。
sub
sub()方法可以用来修改字符串。
import re
content = "54aK54yr5oiR54ix5L2g"
content = re.sub("\d", "", content)
print(content)
这样可以把content中的数字\d都替换成空字符""。
sub()可以用于删除HTML中无关的节点,使得正则表达式变得简单易写。
compile
compile()方法可以将字符串编译成正则表达式对象,以便在后面的匹配中复用。
import re
contents = ['2019-12-25 12:00', '2021-03-05 13:54', '2024-01-26']
pattern = re.compile('\d{2}:\d{2}')
print(type(pattern))
results = []
for content in contents:
results.append(re.sub(pattern, '', content))
print(results[0], results[1], results[2])
这里删除了日期中的时间信息,compile()方法将一个正则表达式转化成一个正则表达式对象(Pattern对象)。
complie还可以传入修饰符,例如re.S等修饰符,这样search(), findall()等方法中就不需要额外传了。可以说compile()方法是给正则表达式做了一层封装,以便我们更好的复用。
httpx库
如果网站强制使用HTTP/2.0协议访问,requests库就无法爬取数据,因为requests库只支持HTTP/1.1,不支持HTTP/2.0协议。
import requests
response = requests.get("https://spa16.scrape.center/")
print(response.text)
抛出了RemoteDisconnected错误,请求失败
httpx库支持HTTP/2.0,requests库的功能它几乎都支持。
安装
要安装支持HTTP、2.0协议的httpx库,可以这样安装:
pip3 install httpx[http2]
基本使用
httpx和rquests库有很多API存在相似之处。
import httpx
response = httpx.get("https://httpbin.org/get")
print(response.status_code)
print(response.headers)
print(response.text)
注意到这里User-Agent的值是python-httpx/0.24.1,代表我们是用httpx请求的。换一个User-Agent再试一次:
import httpx
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, Gecko) ' \
'Chrome/90.0.4430.93 Safari/537.36'
}
response = httpx.get("https://httpbin.org/get", headers=headers)
print(response.text)
更换后User-Agent的生效了。
利用httpx访问spa16.scrape.center/
import httpx
response = httpx.get("https://spa16.scrape.center/")
print(response.text)
也报错了,原因是httpx默认支持HTTP/1.1而不是HTTP/2.0,需要手动声明才能使用HTTP/2.0。
import httpx
client = httpx.Client(http2=True)
response = client.get('https://spa16.scrape.center/')
print(response.text)
这里声明了一个Client对象,同时显式地设置其http2参数为True,这样就开启了对HTTP/2.0的支持
其他方法的请求也是类似的:
import httpx
response = httpx.post('https://www.httpbin.org/post', data={'name': 'John'})
# 响应的状态码
print(response.status_code)
# 响应头
print(response.headers)
# 将文本内容转换成JSON对象
print(response.json())
# 响应体的二进制内容,用于下载图片、视频等
print(response.content)
# 响应体的文本内容
print(response.text)
Client对象
httpx的Client类,可以与requests的Session类进行类比。
import httpx
with httpx.Clinet() as client:
response = client.get('https://www.httpbin.org/get')
print(response)
这种写法等价于:
import httpx
client = httpx.Clinet()
try:
response = client.get('https://www.httpbin.org/get')
finally:
client.close()
这两种写法的运行结果是一样的,只不过第二种需要手动调用close()方法关闭Client对象。
此外在声明Client对象的时候可以指定一些参数,使用该对象发起的请求都会默认带上这些参数配置:
import httpx
url = 'http://www.httpbin.org/headers'
headers = {'User-Agent': 'My-app/0.0.1'}
with httpx.Client(headers=headers) as client:
response = client.get(url)
print(response.json()['headers'])
更多Clinet类的高级用法,请参考官方文档:www.python-httpx.org/advanced/ 。
支持HTTP/2.0
就像最前面的例子一样,想要让Client对象支持HTTP/2.0协议,需要将其参数http2设置为True。
import httpx
client = httpx.Client(http2 = True)
response = client.get('https://www.httpbin.org/get')
print(response.http_version)
response变量的http_version属性是requests库中不存在的属性,代表了访问网站的传输协议。
异步请求
httpx支持异步客户端请求(即AsyncClient),支持Python的async请求模式:
import httpx
import asyncio
async def fetch(url):
async with httpx.AsyncClient(http2=True) as client:
response = await client.get(url)
print(response.text)
asyncio.get_event_loop().run_until_complete(fetch('https://www.httpbin.org/get'))