🐸 青蛙编程网
首页 / Python / 爬虫 / 8. 综合练习

8. 综合练习:全站名言采集器

这是什么:同一个需求做两遍——把 quotes.toscrape.com 全站 10 页、100 条名言(文本、作者、标签)采集存成 CSV。方案 A 用 requests + BeautifulSoup 手写(前四章知识总装),方案 B 用 Scrapy(一个文件 20 行)。做完你会亲手摸到框架的价值。

为什么这么练:只会框架不懂原理,页面一变就束手无策;只会手写不用框架,规模一大全是调度杂活。同一题各写一遍,界限自然清晰。

如何用:先手写版(理解每一步),再 Scrapy 版(体会省了什么),最后对照验收清单自查。

需求清单

# 功能 用到哪章
1 从首页出发,自动翻页爬完 10 页 第 4 章 队列 + urljoin
2 每页提取名言文本、作者、标签列表 第 3 章 选择器
3 带浏览器 UA、超时、失败重试 第 2 章 三件套
4 URL 去重,不重复请求 第 4 章 set
5 请求间隔 1~2 秒,礼貌爬取 第 4 章 sleep
6 结果存 all_quotes.csv(Excel 打开不乱码) 第 4 章 CSV

方案 A:手写版(requests + bs4)

存成 spider_manual.py 运行,约 40 行:

import csv
import random
import time

import requests
from bs4 import BeautifulSoup

HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0"}
BASE = "https://quotes.toscrape.com/"


def fetch(url, session, retries=3):
    for i in range(retries):
        try:
            r = session.get(url, headers=HEADERS, timeout=10)
            r.raise_for_status()
            return r.text
        except requests.RequestException as e:
            print(f"第 {i + 1} 次失败:{e}")
            time.sleep(2 ** i)
    return None


def parse(html):
    soup = BeautifulSoup(html, "html.parser")
    rows = []
    for quote in soup.select("div.quote"):
        rows.append({
            "text": quote.select_one("span.text").get_text(strip=True),
            "author": quote.select_one("small.author").get_text(strip=True),
            "tags": ",".join(a.get_text() for a in quote.select("a.tag")),
        })
    next_page = soup.select_one("li.next a")
    return rows, (next_page["href"] if next_page else None)


def main():
    session = requests.Session()
    seen = set()
    url, all_rows = BASE, []

    while url and url not in seen:
        seen.add(url)
        html = fetch(url, session)
        if html is None:
            break
        rows, next_path = parse(html)
        all_rows.extend(rows)
        print(f"{url} 抓到 {len(rows)} 条,累计 {len(all_rows)} 条")
        time.sleep(random.uniform(1, 2))          # 礼貌间隔
        url = requests.compat.urljoin(BASE, next_path) if next_path else None

    with open("all_quotes.csv", "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=["text", "author", "tags"])
        writer.writeheader()
        writer.writerows(all_rows)
    print(f"完成:{len(all_rows)} 条已写入 all_quotes.csv")


if __name__ == "__main__":
    main()

你会看到:10 行抓取日志逐页刷出,最后 完成:100 条已写入——这一份脚本里,前四章每一章都有一块拼图。

方案 B:Scrapy 版

按第 5 章建好项目后,一个 Spider 文件解决:

import scrapy


class AllQuotesSpider(scrapy.Spider):
    name = "all_quotes"
    start_urls = ["https://quotes.toscrape.com/"]


    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get().strip(),
                "author": quote.css("small.author::text").get(),
                "tags": ",".join(quote.css("a.tag::text").getall()),
            }
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)
scrapy crawl all_quotes -O all_quotes_scrapy.csv

同题对比:框架到底替你干了什么

活儿 手写版位置 Scrapy 版位置
请求 + 重试 + 超时 fetch() 手写重试 settings RETRY_TIMES,一行
翻页调度 while 循环 + urljoin yield response.follow(...)
URL 去重 seen 集合 框架内置
礼貌限速 time.sleep(random...) DOWNLOAD_DELAY = 1.0
存 CSV csv.DictWriter 8 行 命令行 -O xxx.csv
并发 (手写并发还要线程池) CONCURRENT_REQUESTS 配置

结论不是「手写无用」:手写版让你在页面一变、接口一封时知道每个环节在哪;框架版让你在需要规模时不用重造轮子。懂原理的人用框架,如虎添翼;不懂原理的人用框架,只会在报错里迷路。

验收清单

  • [ ] all_quotes.csv 恰好 100 条(含表头 101 行)
  • [ ] Excel 双击打开中文正常显示(utf-8-sig)
  • [ ] 控制台没有任何一次重复 URL 的请求
  • [ ] Scrapy 版导出的 all_quotes_scrapy.csv 与手写版条数一致
  • [ ] 两个版本的 CSV 各自能被 Pandas 一行 pd.read_csv() 读成表格

进阶挑战

  1. 换站实战:把方案 A 改成抓 books.toscrape.com(书名在 h3 a 的 title 属性、价格在 p.price_color),注意书站没有「下一页」按钮的位置差别
  2. SQLite 入库:方案 A 的存储段换成第 4 章的 sqlite3,建表 + executemany
  3. Pandas 分析:读入 CSV 统计哪个作者名言最多、哪个标签出现最频繁(衔接 Pandas 统计与筛选)
  4. 断点续爬:手写版把 seen 集合定期落盘 JSON,程序中断重跑时读回(第 4 章 + JSON 存储)

学完这一栈,你已经能独立完成「原理 → 手写 → 框架」的全链路采集。下一步可以把数据交给 Pandas 分析,或去了解 Playwright 自动化应对 JS 渲染页面 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。