模拟发送 HTTP 请求

GET请求

GET请求是HTTP协议中定义的一种请求方法,用于从服务器获取数据。当发起一个GET请求时,客户端(通常是用户的网页浏览器)向服务器发送一个请求,请求服务器返回指定的资源或数据。当我们用浏览器打开网页时,其实发送的最原始的请求就是 GET 请求

1
2
3
4
5
6
7
import requests 
res = requests.get('http://www.douban.com')
print(res)
print(type(res))
>>>
<Response [200]>
<class 'requests.models.Response'>

可以看到,我们得到的是一个 Response 对象

如果我们要获取网站返回的数据,可以使用 text 或者 content 属性来获取

text:是以字符串的形式返回数据

content:是以二进制的方式返回数据

POST请求

POST请求是HTTP协议中定义的另一种请求方法,用于向服务器提交数据。当发起一个POST请求时,客户端向服务器发送数据,并且这些数据通常作为HTTP请求的一部分(称为请求体)被发送。

对于 POST 请求,一般就是提交一个表单。data 当中,就是需要传递的表单信息,是一个字典类型的数据。

1
r = requests.post('http://www.xxxx.com', data={"key": "value"}) 

header 增强

requests 发送的请求所带的请求头中 User-Agent 会标识为 python 程序发送的请求。

由于这种行为仅仅是消耗服务器资源带来成本,但是不会给对方带来任何好处(搜索引擎的爬虫除外),所以有一些网站会加入反爬机制,通过识别 headers 来拒绝对你的请求进行响应。

所以需要做一些 header 增强。比如:UA,Cookie,host 等等信息。

浏览器访问网站时的 headers 可以在httpbin.org/headers 中得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh-Hans;q=0.9",
"Host": "httpbin.org",
"Referer": "https://link.zhihu.com/?target=https%3A//httpbin.org/headers",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "cross-site",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15",
"X-Amzn-Trace-Id": "Root=1-662b0580-3525d80357418fc75f2d3472"
}
}

将上面的请求头复制下来,传给 requests.get() 函数,即可将请求伪装成浏览器。

requests.get() 的语法是:requests.get(url,kwargs)

其中,url 是我们想要访问的链接,kwargs 是可选参数,包括params、data、json、headers、cookies、auth、files、timeout、proxies、stream、verify、cert等。常用的参数有data、headers。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests

url = 'https://httpbin.org/headers'

headers= {
"Accept": "image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, application/x-ms-xbap, */*",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3",
"Host": "httpbin.org",
"User-Agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; Tablet PC 2.0; wbx 1.0.0; wbxapp 1.0.0; Zoom 3.6.0)",
"X-Amzn-Trace-Id": "Root=1-628b672d-4d6de7f34d15a77960784504"}

response = requests.get(url,headers=headers)


if response.status_code == 200:
print(response.text)

解析 HTML

现在我们已经获取到了网页返回的数据,即 HTML 代码,下面就需要解析 HTML,来提取其中有效的信息。

BeautifulSoup

BeautifulSoup 是 Python 的一个库,最主要的功能是从网页解析数据。

1
2
3
4
from bs4 import BeautifulSoup  # 导入 BeautifulSoup 的方法 
# 可以传入一段字符串,或者传入一个文件句柄。一般都会先用 requests 库获取网页内容,然后使用 soup 解析。
soup = BeautifulSoup(html_doc,'html.parser') # 这里一定要指定解析器,可以使用默认的 html,也可以使用 lxml。
print(soup.prettify()) # 按照标准的缩进格式输出获取的 soup 内容。

bs4BeautifulSoup 库的简称,它是一个用于解析 HTML 和 XML 文档的 Python 库,通常用于网页抓取和数据分析。BeautifulSoup 能够处理复杂的 HTML 网页内容,并提供简单易用的 API 来访问和修改数据。

BeautifulSoup 库的名字来源于一个儿童故事中的角色,这个角色喜欢“把字符串咀嚼成美味的网络抓取汤”,因此得名“BeautifulSoup”。

BeautifulSoup 的一些简单用法

1
2
3
4
5
6
7
print(soup.title)  # 获取文档的 title 
print(soup.title.name) # 获取 title 的 name 属性
print(soup.title.string) # 获取 title 的内容
print(soup.p) # 获取文档中第一个 p 节点
print(soup.p['class']) # 获取第一个 p 节点的 class 内容
print(soup.find_all('a')) # 获取文档中所有的 a 节点,返回一个 list
print(soup.find_all('span', attrs={'style': "color:#ff0000"})) # 获取文档中所有的 span 且 style 符合规则的节点,返回一个 list

在HTML和XML文档中,p 结点通常指的是 <p> 标签创建的元素,它代表一个段落(Paragraph)。<p> 标签是块级元素,用于定义文本的一个段落。在网页中,浏览器会在段落之间添加一些垂直的空白,以区分不同的段落。
例如:

1
2
<p>这是一个段落。</p>
<p>这是另一个段落。</p>

在上面的HTML代码中,每个 <p> 元素都定义了一个文本段落。
同样,a 结点指的是 <a> 标签创建的元素,它代表一个锚点(Anchor)。<a> 标签用于创建超链接,允许用户通过点击跳转到其他网页、文件、位置或其他资源。<a> 标签可以包含文本、图片等任何内容,并且通常有一个 href 属性,指定了链接的目标URL。
例如:

1
<a href="https://www.example.com">访问示例网站</a>

在上面的HTML代码中,<a> 元素创建了一个文本为“访问示例网站”的超链接,当用户点击这个链接时,浏览器会跳转到 https://www.example.com 这个URL。

find_all

在Python的BeautifulSoup库中,find_all方法用于搜索HTML或XML文档,并返回所有匹配指定条件的元素。find_all方法可以接受多种参数,包括要查找的标签名、属性、CSS类等。

1
data = content.find_all('div', attrs={'class': 'cover'})

这行代码的作用是:

  1. content 是一个 BeautifulSoup 对象,它代表了已经解析的HTML文档。
  2. find_all 方法被调用,第一个参数 'div' 指定了要搜索的标签名,即 <div>
  3. attrs={'class': 'cover'} 参数指定了要搜索的 <div> 标签必须具有 class 属性,且属性值包含 cover。这意味着 find_all 方法将返回所有类名为 cover<div> 元素。

结果 data 将是一个列表,包含了文档中所有匹配的 <div> 元素。每个元素都是一个 Tag 对象,你可以对这些对象进行进一步的操作,例如提取文本、属性或其他嵌套的标签。

例如,如果您想要遍历这些 <div> 元素并打印它们的文本内容,可以这样做:

1
2
for div in data:
print(div.get_text())

这将会打印出每个类名为 cover<div> 元素中的文本内容。

<div>元素是HTML文档中的一个块级元素,它用于定义文档中的一个区域或部分,并且可以用来组合其他HTML元素。<div>是 “division” 的缩写,意为“分区”。

find

BeautifulSoup 中,find 方法用于搜索当前元素下的子元素,并返回第一个匹配的元素。如果找到了多个匹配的元素,find 方法只返回第一个匹配的元素。如果没有找到匹配的元素,则返回 None

例如,假设我们有以下HTML代码片段:

1
2
3
4
<div class="cover">
<img src="image1.jpg" alt="图片1">
<img src="image2.jpg" alt="图片2">
</div>

如果我们已经有一个 BeautifulSoup 对象 d,它代表上面的 <div> 元素,那么我们可以使用 d.find('img') 来找到第一个 <img> 子元素。

我们可以进一步从 img_tag 中获取 src 属性,得到图片的URL:

1
img_url = img_tag['src']

在这里,img_url 的值将是 "image1.jpg"

get_text

BeautifulSoup 中,get_text() 方法是一个用于提取标签内文本内容的方法。它将返回一个字符串,该字符串包含指定标签及其所有子标签的文本内容,而不包含任何HTML标签或属性。

get_text() 方法可以用于任何 Tag 对象,它会递归地提取所有子标签的文本,并将它们连接成一个字符串。
这里有一些 get_text() 方法的基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from bs4 import BeautifulSoup
# 假设我们有以下HTML内容
html_doc = """
<html>
<head>
<title>Page Title</title>
</head>
<body>
<div>
<p>这是一个段落。</p>
<a href="http://example.com">这是一个链接</a>
</div>
</body>
</html>
"""
# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(html_doc, 'html.parser')
# 获取整个文档的文本内容
text = soup.get_text()
print(text)

输出将会是:

1
2
3
Page Title
这是一个段落。
这是一个链接

你也可以在特定的标签上调用 get_text() 方法来提取该标签及其子标签的文本:
1
2
3
# 获取特定标签 <div> 的文本内容
div_text = soup.div.get_text()
print(div_text)

输出将会是:
1
2
这是一个段落。
这是一个链接

get_text() 方法还有一些参数,可以用来控制如何提取文本:

  • strip: 布尔值,默认为 False,表示是否去除文本前后的空白字符。
  • separator: 字符串,用于连接各个文本片段的分隔符,默认为 “”,表示不添加分隔符。

下载图片

1
2
3
4
5
6
7
8
def download_picture(pic_l): 
if not os.path.exists(r'picture'):
os.mkdir(r'picture')
for i in pic_l:
pic = requests.get(i)
p_name = i.split('/')[7]
with open('picture\\' + p_name, 'wb') as f:
f.write(pic.content)
  1. if not os.path.exists(r'picture'):
    • 这个条件判断语句检查在当前目录下是否存在一个名为 picture 的文件夹。os.path.exists是一个检查路径是否存在的函数,r'picture' 表示 picture 文件夹的路径,r 前缀表示这是一个原始字符串,不处理反斜杠 \ 作为转义字符。
  2. os.mkdir(r'picture')
    • 如果上述条件成立(即 picture 文件夹不存在),这行代码会创建一个名为 picture 的新文件夹。
  3. pic = requests.get(i)
    • 在循环内部,这行代码使用 requests.get 函数从互联网上获取(下载)URL i 指向的图片内容,并将响应内容赋值给变量 pic
  4. p_name = i.split('/')[7]
    • 这行代码将URL i 按照斜杠 / 分割成多个部分,并取出第8个部分(列表索引为7)作为图片的文件名。这里假设URL的第八部分是图片的文件名。
  5. with open('picture\\' + p_name, 'wb') as f:
    • 这行代码打开一个文件,文件路径是 picture 文件夹加上图片的文件名 p_name,文件以二进制写模式(‘wb’)打开。with 语句确保文件使用后会被正确关闭。变量 f 是打开文件的引用。
  6. f.write(pic.content)
    • 最后,这行代码将下载的图片内容 pic.content 写入到文件 f 中,完成图片的保存。