今天开始学习 Python 爬虫。爬虫的主要工作可以简单概括为:

发送请求 → 获取网页 → 解析内容 → 提取数据

这几个步骤看起来简单,但每一步都有很多细节。今天主要学习了 requests 库、网页响应对象、HTML 节点、XPath 路径表达式以及常用 XPath 函数。


一、什么是爬虫

爬虫程序可以自动访问网页,并从网页中提取我们需要的信息。

例如:

  • 获取新闻标题;
  • 抓取商品名称和价格;
  • 提取电影评分;
  • 下载图片;
  • 统计网页中的链接;
  • 获取公开网页中的表格数据。

浏览器访问网页时,背后也会向服务器发送 HTTP 请求。服务器处理请求后,会返回 HTML、JSON、图片或其他内容。Python 爬虫做的事情,就是用代码模拟这种请求,再解析服务器返回的数据。

一个简单的爬虫流程如下:

准备目标网址
    ↓
使用 requests 发送请求
    ↓
获取 response 响应对象
    ↓
读取 HTML 内容
    ↓
使用 XPath 定位节点
    ↓
清洗并保存数据

需要注意,爬虫只能抓取公开且允许访问的内容,不能绕过登录、验证码、权限控制或网站的访问限制。


二、使用 requests 发送 GET 请求

安装 requests

pip install requests

发送 GET 请求:

import requests

url = "https://www.example.com"

response = requests.get(url)

print(response)

response 是一个响应对象,类型通常是:

requests.models.Response

如果输出:

<Response [200]>

说明服务器正常返回了响应。

常见状态码包括:

状态码 含义
200 请求成功
301、302 重定向
403 没有权限访问
404 页面不存在
500 服务器内部错误

发送请求时,建议设置超时时间,避免服务器迟迟不响应:

response = requests.get(
    url,
    timeout=10
)

也可以先判断请求是否成功:

response.raise_for_status()

如果响应状态码是 4xx 或 5xx,raise_for_status() 会抛出异常。


三、Response 响应对象

服务器返回的内容会保存在 response 对象中。

1. response.encoding

表示响应内容使用的编码格式:

response.encoding = "utf-8"

如果网页出现中文乱码,可以尝试设置正确编码。

2. response.text

以字符串形式返回网页源码:

html = response.text
print(html)

这是解析 HTML 时最常用的属性。

3. response.content

以二进制形式返回响应内容:

content = response.content

content 适合处理图片、压缩文件或其他非文本数据:

with open("image.jpg", "wb") as file:
    file.write(response.content)

4. response.url

返回最终访问的网址:

print(response.url)

当网页发生重定向时,最终 URL 可能与最初请求的 URL 不同。

5. response.status_code

返回 HTTP 状态码:

print(response.status_code)

6. response.headers

返回响应头:

print(response.headers)

响应头中可能包含内容类型、服务器信息、编码格式和缓存信息。


四、设置请求参数

如果网址中有查询参数,可以使用 params

params = {
    "page": 1,
    "keyword": "Python"
}

response = requests.get(
    "https://www.example.com/search",
    params=params
)

print(response.url)

Requests 会自动把参数拼接到 URL 后面。

有些网站会根据请求头判断访问者是不是浏览器。可以设置 headers

headers = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
    )
}

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

如果需要使用代理,可以通过 proxies 参数:

proxies = {
    "http": "http://127.0.0.1:7890",
    "https": "http://127.0.0.1:7890"
}

response = requests.get(
    url,
    proxies=proxies,
    timeout=10
)

代理地址需要根据实际环境填写,没有代理时不需要设置。


五、POST 请求

GET 请求通常用于获取数据,POST 请求常用于提交数据。

data = {
    "username": "test",
    "password": "123456"
}

response = requests.post(
    "https://www.example.com/login",
    data=data,
    timeout=10
)

如果接口接收 JSON 数据,可以使用:

payload = {
    "name": "小明",
    "age": 18
}

response = requests.post(
    url,
    json=payload,
    timeout=10
)

实际爬虫中,要根据网页请求方式选择 GET 或 POST。不能看到一个网址就默认使用 GET。


六、认识 HTML 节点

网页本质上是由 HTML 标签组成的。

例如:

<html>
    <body>
        <div class="article">
            <h1>Python 爬虫入门</h1>
            <p>这是文章内容</p>
        </div>
    </body>
</html>

这些标签在 XPath 中叫作节点。

常见节点类型包括:

1. 元素节点

例如:

<div></div>
<h1></h1>
<p></p>

2. 属性节点

例如:

<div class="article"></div>

这里的 class="article" 就是属性。

3. 文本节点

例如:

<button>百度一下</button>

其中的“百度一下”就是文本节点。

4. 根节点

整个 HTML 文档的最外层节点通常是根节点:

<html></html>

节点之间存在层级关系:

  • 父节点;
  • 子节点;
  • 兄弟节点;
  • 根节点。

例如:

<div>
    <p>第一段</p>
    <p>第二段</p>
</div>

div 是两个 p 的父节点,两个 p 互相是兄弟节点。


七、使用 lxml 解析网页

Python 中常使用 lxml 配合 XPath 解析网页:

pip install lxml

基本代码如下:

import requests
from lxml import etree

url = "https://www.example.com"

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

response.encoding = "utf-8"

html = etree.HTML(
    response.text
)

result = html.xpath(
    "//h1/text()"
)

print(result)

etree.HTML() 会把网页源码转换成可以使用 XPath 查询的 HTML 树。

XPath 查询的结果通常是列表,即使只找到一个节点,也可能返回:

["Python 爬虫入门"]

八、XPath 的基本语法

1. 选择元素节点

/html/body/div

表示从根节点开始,依次查找 htmlbodydiv

2. 使用 //

//div

表示查找文档中任意位置的 div,不需要写出完整层级。

3. 使用 .

.

表示当前节点。

4. 使用 ..

..

表示当前节点的父节点。

5. 使用 @

//div/@class

表示获取 div 节点的 class 属性。

6. 获取文本

//button/text()

表示获取 button 节点中的文本。

例如:

titles = html.xpath(
    "//h1/text()"
)

for title in titles:
    print(title.strip())

九、XPath 的节点选择

可以按照标签、属性和文本选择节点:

# 选择所有div元素
html.xpath("//div")

# 选择class为article的div
html.xpath("//div[@class='article']")

# 选择所有包含class属性的div
html.xpath("//div[@class]")

# 选择button中的文本
html.xpath("//button/text()")

如果属性值包含多个 class,直接使用等号可能不够准确:

<div class="article item"></div>

可以使用 contains()

//div[contains(@class, 'article')]

在代码中:

items = html.xpath(
    "//div[contains(@class, 'article')]"
)

十、XPath 运算符

XPath 中常见的路径运算符如下:

1. 单斜杠 /

/html/body/div

表示按照完整层级查找。

2. 双斜杠 //

//div

表示从当前范围内任意位置查找 div

3. 点 .

.

表示当前节点。

4. 双点 ..

..

表示当前节点的父节点。

5. @

//a/@href

表示获取链接节点的 href 属性。

例如:

links = html.xpath(
    "//a/@href"
)

for link in links:
    print(link)

十一、XPath 下标和谓词

XPath 可以使用中括号进行筛选。

选择第一个 li

//ul/li[1]

选择最后一个 li

//ul/li[last()]

选择前四个 li

//ul/li[position() < 5]

选择包含指定属性的节点:

//div[@class='item']

选择文本内容:

//button[text()='百度一下']

对应 Python 代码:

items = html.xpath(
    "//ul/li[position() < 5]/text()"
)

print(items)

需要注意,XPath 下标从 1 开始,而 Python 列表下标从 0 开始。这是初学者经常混淆的地方。


十二、XPath 常用字符串函数

1. contains()

检查字符串是否包含指定内容:

//div[contains(@class, 'item')]

Python 示例:

nodes = html.xpath(
    "//div[contains(@class, 'item')]"
)

2. starts-with()

检查字符串是否以指定内容开头:

//a[starts-with(@href, 'https')]

3. string-length()

返回字符串长度:

//p[string-length(text()) > 10]

这表示选择文本长度大于 10 的 p 节点。

这些函数适合处理 class 名称不完全固定、链接前缀相同或者文本长度不同的网页。


十三、XPath 数字函数和逻辑运算符

XPath 也支持一些数字函数:

sum()
floor()
ceiling()

例如:

sum(//span/@data-price)

可以尝试计算多个属性值的总和。

逻辑运算符包括:

and
or
not()

例如:

//div[@class='item' and @data-type='book']

表示同时满足两个条件。

//div[@class='book' or @class='movie']

表示满足其中一个条件。

//div[not(@hidden)]

表示选择没有 hidden 属性的节点。


十四、一个简单的网页提取案例

下面模拟提取网页中的文章标题和链接:

import requests
from lxml import etree

url = "https://www.example.com"

headers = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
    )
}

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

response.raise_for_status()
response.encoding = "utf-8"

html = etree.HTML(response.text)

titles = html.xpath(
    "//h2[contains(@class, 'title')]/text()"
)

links = html.xpath(
    "//h2[contains(@class, 'title')]/a/@href"
)

for title, link in zip(titles, links):
    print({
        "title": title.strip(),
        "link": link
    })

这里的流程是:

  1. 使用 requests.get() 请求网页;
  2. 使用 raise_for_status() 检查请求是否成功;
  3. 设置编码;
  4. 将网页源码转换为 HTML 树;
  5. 使用 XPath 获取文章标题;
  6. 使用 XPath 获取链接;
  7. 使用 zip() 将标题和链接对应起来。

如果网页结构发生变化,XPath 也需要相应调整。


十五、爬虫中的常见问题

1. 网页乱码

可以尝试:

response.encoding = "utf-8"

有些网站的编码可以从响应头或网页源码中判断。

2. 请求超时

应该设置:

timeout=10

并使用异常处理:

try:
    response = requests.get(
        url,
        timeout=10
    )
except requests.RequestException as error:
    print(f"请求失败:{error}")

3. XPath 找不到数据

首先检查:

  • XPath 是否写错;
  • 网页是否使用 JavaScript 动态加载;
  • 节点是否位于 iframe 中;
  • 返回的网页源码是否与浏览器看到的内容相同;
  • class 是否包含多个值。

4. 请求被拒绝

可以检查状态码:

print(response.status_code)

也可以设置合理的 User-Agent,并降低请求频率。


十六、总结

今天学习的内容可以分成两部分。

第一部分是 requests

  • get() 发送 GET 请求;
  • post() 发送 POST 请求;
  • text 获取字符串形式的网页源码;
  • content 获取二进制数据;
  • status_code 获取状态码;
  • headers 获取响应头;
  • encoding 设置编码格式;
  • url 获取最终访问地址;
  • params 传递查询参数;
  • proxies 设置代理。

第二部分是 XPath:

  • / 按照完整路径查找;
  • // 从任意位置查找;
  • . 表示当前节点;
  • .. 表示父节点;
  • @ 选择属性;
  • text() 获取文本;
  • contains() 检查是否包含字符串;
  • starts-with() 检查字符串开头;
  • last() 选择最后一个节点;
  • position() 按位置筛选节点;
  • andornot() 进行逻辑判断。

爬虫的核心并不是简单地“复制网页内容”,而是先理解网页结构,再选择合适的请求和解析方式。掌握 requests 和 XPath 后,就可以完成很多静态网页的数据提取任务。后续还可以继续学习 BeautifulSoup、正则表达式、动态网页处理,以及数据保存到 CSV、Excel 和数据库。

看到最后奖励一个后羿采集器,直接文章搜索下载就行,爬虫神器,嘿嘿嘿

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐