Python 爬虫入门怎么练?从请求网页到提取标题、去重与保存

编程狮(w3cschool.cn) 2026-09-10 14:50:19 浏览数 (10)
反馈

第一次写 Python 爬虫,先把请求、解析和保存拆开验证。你可以先处理一段本地 HTML,再接入允许访问的页面;这样标题为空时,就能区分是页面没下载到,还是提取规则写错。

Python 爬虫入门先解析,再接网络

“能打开网页”与“程序能拿到目标内容”不是同一个条件。浏览器可能执行脚本、携带登录状态,而普通请求只得到最初的响应。另一个常见误区是先并发抓很多页,最后才发现保存的全是空标题。本文用一个小型课程列表完成离线解析,再演示单页下载,最后保存 JSON。编程狮把范围限定为公开静态页面,使用 Python 3、Requests 2 与 Beautiful Soup 4;不涉及登录绕过或浏览器自动化。

一、先约定数据形状,别让解析和保存混在一起

一个标题采集任务,最少需要 title 与 url 两个字段。标题应该是清理过空白的可读文本,链接应该是可比较的绝对地址。只有两项都符合预期,才进入最终结果。

把流程想成三段流水:请求负责获得原料,解析负责提取字段,保存负责写出文件。请求返回了错误页面,解析器再精巧也没用;解析把所有空标题忽略掉,保存成功也不代表采集成功。最好让每一段都有独立的输入和输出。

练习环境可以创建虚拟环境,再使用对应解释器安装依赖。Windows 常见启动器是 py,macOS 或 Linux 常见命令是 python3;以下环境创建命令按系统二选一,后面的安装均指向刚创建的环境。

py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install "requests>=2.32,<3" "beautifulsoup4>=4.12,<5"

python3 -m venv .venv
.venv/bin/python -m pip install "requests>=2.32,<3" "beautifulsoup4>=4.12,<5"

如果你还不熟悉列表、字典和异常,可以先补 Python3 教程 的基础内容。本文示例把解析封装成函数,便于之后换网站时只修改这一处。

二、离线提取标题与链接,先看清嵌套文本

把下列代码保存为 parse_courses.py。它没有网络请求,HTML 里故意放了嵌套标签、相对链接、空标题和重复项,用来检查你的提取规则。

from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlsplit, urldefrag

HTML = """
<ul class="courses">
 <li><a href="/learn/python"><strong>Python</strong> 入门</a></li>
 <li><a href="/learn/js#intro"> JavaScript 基础 </a></li>
 <li><a href="/learn/python">重复入口</a></li>
 <li><a href="/empty"> </a></li>
</ul>
"""

def parse_courses(html, base_url):
    soup = BeautifulSoup(html, "html.parser")
    rows, seen = [], set()
    for anchor in soup.select(".courses a[href]"):
        title = " ".join(anchor.stripped_strings)
        url = urldefrag(urljoin(base_url, anchor["href"]))[0]
        if not title or urlsplit(url).scheme not in {"http", "https"}:
            continue
        if url in seen:
            continue
        seen.add(url)
        rows.append({"title": title, "url": url})
    return rows

if __name__ == "__main__":
    rows = parse_courses(HTML, "https://example.test/")
    assert len(rows) == 2
    assert rows[0]["title"] == "Python 入门"
    assert rows[1]["url"] == "https://example.test/learn/js"
    print(rows)

标题中的 strong 标签不会让文本丢失,因为 stripped_strings 会遍历相关文本片段,而不是只读取第一个直接文本节点。用空格连接片段,是本文明确选择的清洗规则;如果你采集中文连续文本,是否保留空格需要根据原页面检查。

urljoin 将相对地址转换成绝对地址,urldefrag 去掉片段,随后按 URL 去重。示例保留先出现的标题。这不是所有网站都适用的唯一规则:如果同一文档用片段标识不同章节,去片段会合并本来不同的数据,应保留片段或改用章节编号。

三、接入单页请求时,先确认响应是不是目标页面

离线例子通过后,再尝试单页下载。下面使用练习站点的公开页面,执行前仍应确认站点当前允许的访问范围。把代码保存为 fetch_page.py,使用虚拟环境解释器运行。

from pathlib import Path
import requests
from bs4 import BeautifulSoup

url = "https://quotes.toscrape.com/"
response = requests.get(
    url, timeout=(5, 15),
    headers={"User-Agent": "w3cschool-learning/1.0"}
)
response.raise_for_status()
Path("page.html").write_bytes(response.content)
soup = BeautifulSoup(response.content, "html.parser")
title = soup.title.get_text(" ", strip=True) if soup.title else ""
if not title:
    raise ValueError("页面没有可用的 title")
print(response.status_code, response.url, title)

这段程序只下载一次,并保存原始响应字节用于复查。连接超时与读取超时分别设置,但不是整个任务的绝对总时长限制。raise_for_status 会把常见错误状态转换成异常;状态成功仍可能拿到登录页或提示页,所以还需检查最终地址和标题。

如果浏览器可见课程,保存的 page.html 却没有目标标签,先比较网页源代码和浏览器运行后的结构。Requests 不执行 JavaScript,不能因为选择器返回空就一味改 CSS 规则。请求参数与响应对象的用法可参考 Python requests 教程

乱码也要分层检查:原始字节是否正确、服务器声明的编码是否可靠、保存和打开文件时是否采用同一编码。不要不看响应就强制把所有网页设成 UTF-8;示例把字节交给解析器,真实站点仍需根据声明与样本验证。

四、保存 JSON 前,检查空值、数量和重复规则

将下面脚本与 parse_courses.py 放在同一目录,保存为 save_courses.py。它复用已经验证过的离线解析结果,把读取和写入接成完整小闭环。

import json
from pathlib import Path
from parse_courses import HTML, parse_courses

rows = parse_courses(HTML, "https://example.test/")
if not rows:
    raise ValueError("没有提取到课程,停止写出结果")
output = Path("courses.json")
output.write_text(
    json.dumps(rows, ensure_ascii=False, indent=2),
    encoding="utf-8"
)
loaded = json.loads(output.read_text(encoding="utf-8"))
assert loaded == rows
print(f"已保存 {len(loaded)} 条")

ensure_ascii=False 让中文以原文字形保存,encoding 决定文件字节编码,两者作用不同。写完再读回并比较,能发现输出路径不对、文件被截断或序列化方式与预期不一致等问题。

扩展到多页时,把“请求成功数”“提取记录数”“空标题数”“去重后记录数”分别统计。即使脚本退出码为零,如果提取数量从一百骤降到二,也应视为需要检查的变化。数据质量不能只看文件是否存在。

每次请求之间至少留出两秒,遇到限流使用有上限的指数退避,并遵守站点给出的等待提示。多页任务需要独立处理失败 URL,不能把失败页当空列表吞掉。练习阶段限制页数,先验证两页再扩大范围,能明显减少无效访问与返工。

标题为空,应该先查哪一层?

总结

Python 爬虫入门的关键,是让下载、解析和保存各自可验证。先用本地 HTML 验证嵌套标题、相对地址和重复项,再下载一个允许访问的页面,最后把记录写出并读回。

这条路线让你在标题为空时知道该看响应还是选择器,在重复数据出现时知道该查 URL 规则还是分页逻辑。完成本文后,下一步可以为解析函数增加更多页面样本,再考虑 Scrapy 的请求调度和流水线;无需把一个单页练习立即扩展成难以调试的大型采集系统。

延伸学习

  1. 跟随 Python3 爬虫入门与实践 继续练习采集流程。
  2. HTML 教程 理解标签嵌套与页面结构。
  3. 阅读 Python 读取 JSON 文件的四种方法,补齐结果文件的后续处理。

常见问题

Q:为什么浏览器看到内容,程序却提取不到?

A:可能是脚本渲染、登录状态或响应页面不同。先检查保存的原始 HTML,确认目标内容确实存在,再修改选择器,不要直接认定是解析库失效。

Q:去重时能把查询参数全部删掉吗?

A:不能一概删除。查询参数可能标识不同文章或分页。应只移除确认无业务含义的参数,保留影响内容身份的部分,并用样本验证规则。

Q:空结果可以直接覆盖上一份文件吗?

A:不建议默认这样做。空结果可能来自页面改版或临时错误。应先判断空结果是否符合预期,保留上一份有效结果,再决定是否更新正式文件。

1 人点赞