| Day 26 | 网络爬虫:requests 与 BeautifulSoup |

1 requests 与 Response 对象 —— 爬虫的第一步

是什么?为什么学?

requests 是 Python 最流行的 HTTP 请求库。爬虫的第一步永远是「把网页拿回来」:requests.get(url) 向服务器发请求,返回 Response 对象,里面装着状态码、网页文本、编码。所有爬虫和 API 调用都以它为起点。我们每天上网看到的网页,本质是一堆 HTML 文本。爬虫就是「用程序代替人去看网页」:先请求网页拿到 HTML,再从 HTML 里把想要的信息挑出来。

底层原理

requests.get(url) 内部:建立连接 → 发送请求头 → 服务器返回响应 → 封装成 Response。三个常用属性:resp.status_code200 成功、404 不存在、403 被拒)、resp.text(网页源码)、resp.encoding(编码,不对中文就乱码)。**网络不可靠,要加 timeouttry/except**。

生活类比

打电话requests.get(url) 是拨号加问话,Response 是对方答复——先听语气(200 是「好的」),再听内容(resp.text)。

注意事项总结

1 请求网页,看状态码与内容(需要联网):

import requests

resp = requests.get("https://example.com", timeout=10)
print("状态码:", resp.status_code)      # 200 表示成功
print("编码:", resp.encoding)
print("网页前 80 个字符:")
print(resp.text[:80])

requests.get("https://example.com", timeout=10) 发请求(timeout=10 是超时保护);resp.status_code 判断成败;resp.text 是 HTML 源码——这就是爬虫的「原料」。

2 加 User-Agent + 手动指定编码(中文不乱码的关键):

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
}
resp = requests.get("https://www.weather.com.cn/weather/101010100.shtml", headers=headers, timeout=10)
resp.encoding = "utf-8"     # 手动指定编码,中文才不会乱

很多网站会检查请求来源,不带浏览器标识(User-Agent)可能被拒。网页可能是 UTF-8 的,但 requests 可能猜错编码,手动指定 resp.encoding = "utf-8" 中文才不乱。

易错点

  • 错误写法:不写 timeout → 问题:服务器不响应时无限卡住 → 正确写法:requests.get(url, timeout=10)
  • 错误写法:URL 忘了 https:// → 问题:报 InvalidURL / MissingSchema → 正确写法:URL 写全:https://example.com
  • 错误写法:中文乱码 → 问题:网页编码没设对 → 正确写法:手动指定 resp.encoding = "utf-8"

记忆口诀:get 拿网页,resp 是答复;200 是成功,404 没人接;超时兜底别忘 try。

2 BeautifulSoup 对象树 —— 把 HTML 变成「档案柜」

是什么?为什么学?

BeautifulSoup(bs4) 是解析 HTML 的库。网页源码是一大坨字符串,直接切片提取非常痛苦;BeautifulSoup 把 HTML 解析成一棵对象树(标签的嵌套结构),之后用「找标签」的方式取内容。配合 requests 就是完整的「抓取 + 解析」链路。

底层原理

BeautifulSoup(html, "html.parser") 把字符串变成对象树:每个标签是一个 Tag 对象。常用操作:soup.find("h1") 找第一个、soup.find_all("li")所有tag.get_text() 取文字、tag["href"] 取属性。**注意 class 是 Python 关键字,按 class 查找要写 class_**。

生活类比

档案管理员:HTML 是一堆杂乱的纸(字符串),管理员整理成档案柜(对象树)——找「所有标题」查 find_all("h2"),几秒搞定。

注意事项总结

1 解析本地 HTML,提取标题、列表项、链接(离线可跑):

from bs4 import BeautifulSoup

html = """
<html><body>
<h1>今日天气</h1>
<ul>
  <li>周一 晴 32℃</li>
  <li>周二 多云 30℃</li>
</ul>
<a href="/detail">查看详情</a>
</body></html>
"""
soup = BeautifulSoup(html, "html.parser")
print(soup.find("h1").get_text())              # 第一个 h1 的文字
for item in soup.find_all("li"):               # 所有 li 标签
    print(item.get_text())
print(soup.find("a")["href"])                  # 取 a 的链接属性

运行结果:

今日天气
周一 晴 32℃
周二 多云 30℃
/detail

BeautifulSoup(html, "html.parser") 把字符串解析成对象树(必须传解析器参数);soup.find("h1") 找第一个 h1,.get_text() 取文字;find_all("li") 返回列表配合 for 循环;["href"] 取属性。

易错点

  • 错误写法:soup.find("p", class="temp") → 问题:class 是关键字,语法报错 → 正确写法:用 class_="temp"
  • 错误写法:soup.find(...) 没找到就 .get_text() → 问题:返回 None,报 AttributeError → 正确写法:先判断 if tag is not None:
  • 错误写法:BeautifulSoup(html) 不写解析器 → 问题:告警「未指定解析器」 → 正确写法:显式写 BeautifulSoup(html, "html.parser")

记忆口诀:BeautifulSoup 建档案,find 一个 find_all 一堆;class 加下划线,text 取文本,None 要先判断。

3 CSS 选择器与列表爬取套路 —— 精确定位、批量提取

是什么?为什么学?

find / find_all 只能按标签名或单个属性找,复杂页面不够用。CSS 选择器是网页开发的标准定位语法;而新闻、商品、电影等几乎所有列表页结构相同——一个容器里重复 N 个条目。学会选择器 + 「先选条目、再取字段」,任何列表页都能爬。

底层原理

选择器语法:标签 按标签名、#id 按 id(如 #tq_zx)、.class 按 class(如 .wea)、空格 表示「里面的」(后代)。soup.select("选择器") 返回所有匹配(列表);soup.select_one("选择器") 返回第一个(没有则 None)。列表爬取两步走:外层 select 拿所有条目,内层 select_one 取每个字段;get_text(strip=True) 自动去首尾空白。

生活类比

快递柜取件码#tq_zx 是「3 号柜(id)」,.wea 是「蓝色标签的格子(class)」,ul.t li 是「1 区柜子里第二层的包裹」。再像整理通讯录:先抽出整本(select 拿条目),再逐页抄姓名电话(每条目取字段)。

注意事项总结

1 CSS 选择器定位模拟的天气列表(离线可跑):

from bs4 import BeautifulSoup

html = """
<ul class="t clearfix">
  <li class="sky">
    <h1>3日(今天)</h1>
    <p class="wea">雷阵雨</p>
    <p class="tem"><span>32</span>/<i>24℃</i></p>
  </li>
</ul>
"""

soup = BeautifulSoup(html, "html.parser")
li = soup.select_one("ul.t li")            # class=t 的 ul 里第一个 li
print(li.select_one("h1").get_text())      # 日期
print(li.select_one("p.wea").get_text())   # 天气(class=wea)
print(li.select_one("span").get_text())    # 最高温

运行结果:

3日(今天)
雷阵雨
32

"ul.t li" 是 CSS 选择器(ul.t 表示 class 为 t 的 ul,空格后 li 表示里面的 li);select_one 只取第一个;拿到 lili.select_one(...)局部继续找——元素上也能调同样的方法,这是爬虫常用技巧。

2 列表爬取套路——模拟新闻列表的标题与链接(离线可跑):

html = """
<div id="news">
  <dl><dd><a href="/n1">暴雨橙色预警发布</a><span>10:25</span></dd></dl>
  <dl><dd><a href="/n2">北京雨后闷热持续</a><span>11:29</span></dd></dl>
</div>
"""

soup = BeautifulSoup(html, "html.parser")
items = soup.select("#news dl")            # 第一步:所有条目
print(f"共 {len(items)} 条")
for item in items:                         # 第二步:逐条取字段
    title = item.select_one("a").get_text(strip=True)
    href = item.select_one("a")["href"]
    print(title, "->", href)

soup.select("#news dl") 用 id 选择器定位容器,取里面所有 dl 条目;循环里 item.select_one("a") 取第一条链接,get_text(strip=True) 拿标题、["href"] 拿链接。

易错点

  • 错误写法:需要单个却用 select → 问题:返回列表,.get_text() 报错 → 正确写法:只要一个用 select_one,要全部用 select
  • 错误写法:class 漏写 . → 问题:按标签名找,匹配不到 → 正确写法:class 前必须有点:p.wea;id 用 #
  • 错误写法:内层也用 select("a") → 问题:拿到列表,["href"] 报错 → 正确写法:每项只要一个,内层用 select_one
  • 错误写法:链接是相对路径(/n1) → 问题:直接拼接丢域名 → 正确写法:拼全:"https://example.com" + href

记忆口诀:select 一堆 select_one 一个;# 是 id,. 是 class,空格是「里面的」;列表先选条目再取字段。

4 robots.txt 与君子协议 —— 做个有礼貌的爬虫

是什么?为什么学?

robots.txt 是网站放在根目录的「说明文件」,告诉爬虫哪些路径可以爬、哪些不可以。写爬虫前先看它、遵守它,配合 time.sleep 控制频率,是爬虫工程师的基本素养——乱爬会被封 IP,甚至惹上官司。

底层原理

robots.txt 规则:User-agent 声明针对哪个爬虫(* 表示所有);Disallow 禁止访问的路径;Allow 例外允许。君子协议是社区约定而非法律:有 robots 就遵守、控制频率(每请求间隔几秒)、数据只用于学习研究、不商用不公开传播、网站明确反对就停止。

生活类比

图书馆规则:robots.txt 是门口「入馆须知」,君子协议是「安静、不占座」的公共礼仪。你不违法,但没礼貌会被管理员盯上。

注意事项总结

1 查看目标网站的 robots.txt(需要联网):

import requests

resp = requests.get("https://www.weather.com.cn/robots.txt", timeout=5)
print("状态码:", resp.status_code)
print(resp.text[:200])      # 有内容就说明网站声明了规则

直接请求 robots 文件,resp.text 打印内容。注意:部分网站没有 robots.txt(返回 404),但没声明不代表可以随便爬

2 频率控制 + 异常兜底的标准姿势(离线可跑):

import time

urls = ["https://example.com/page1", "https://example.com/page2"]
for url in urls:
    try:
        print(f"抓取:{url}(模拟成功)")
    except Exception as e:       # 实际应接住 requests 的具体异常
        print(f"抓取失败:{e}")
    time.sleep(2)                # 每次请求后休息 2 秒

time.sleep(2)频率控制核心——每请求一次停 2 秒,不给服务器压力;try/except 把单个失败兜住,一个失败不影响后面

易错点

  • 错误写法:循环几百个 URL 不 sleep → 问题:瞬间打爆服务器,IP 被封 → 正确写法:每次请求后 time.sleep(1~3)
  • 错误写法:完全不看 robots.txt → 问题:可能爬到禁止路径 → 正确写法:请求前先读 目标网站/robots.txt
  • 错误写法:一个页面失败就崩溃 → 问题:一个 404 中断全部任务 → 正确写法:try/except 接住,继续下一个。

记忆口诀:先看 robots 再动手,sleep 控频不轰炸;except 兜底不崩溃,君子协议记心间。

5 动手实践:爬取新闻列表并保存 CSV

爬取中国天气网首页的「天气资讯」新闻列表(标题、链接、来源、时间),保存成 CSV,方便后续分析或生成报告。

# news_crawler.py —— 天气资讯新闻列表爬虫
import csv
import time
import requests
from bs4 import BeautifulSoup

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
}

url = "https://www.weather.com.cn/weather/101010100.shtml"

def crawl_news():
    news_list = []
    try:
        resp = requests.get(url, headers=headers, timeout=10)
        resp.raise_for_status()
        resp.encoding = "utf-8"
        soup = BeautifulSoup(resp.text, "html.parser")

        for item in soup.select("#tq_zx dl"):
            a = item.select_one("dd a")
            if not a:
                continue
            title = a.get_text(strip=True)
            href = a["href"]
            # 来源和时间在同一行,如"中国天气网   2026-08-03 11:29"
            source_line = item.select_one("dd span").get_text(strip=True)
            parts = source_line.split()
            source = parts[0] if parts else ""
            pub_time = parts[1] if len(parts) > 1 else ""
            news_list.append([title, href, source, pub_time])

    except requests.exceptions.RequestException as e:
        print(f"请求失败:{e}")
    except AttributeError:
        print("页面结构可能改版了,请检查选择器")

    return news_list

def save_csv(news_list):
    with open("news_list.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["标题", "链接", "来源", "时间"])
        writer.writerows(news_list)
    print(f"已保存 {len(news_list)} 条新闻到 news_list.csv")

if __name__ == "__main__":
    news = crawl_news()
    for n in news[:3]:
        print(n[0], "|", n[2], n[3])
    save_csv(news)
    time.sleep(1)

运行效果:

北京雨后湿漉漉 桑拿天威力不减 | 中国天气网 2026-08-03 11:29
河北大范围降雨持续 今天石家庄张家口承德等地雨势猛烈 | 中国天气网 2026-08-03 10:56
暴雨橙色预警:陕西重庆等5省区市部分地区有大暴雨 局地特大暴雨 | 中国天气网 2026-08-03 10:25
已保存 5 条新闻到 news_list.csv

打开 news_list.csv 验证数据。整个程序就是一个标准的爬虫闭环:请求 → 解析 → 提取 → 保存 → 休息

升级挑战:把 news_list.csv 里的新闻链接逐个请求一遍,抓取每篇新闻的正文前 200 字(记得每次 time.sleep(2),并判断页面结构)。

今日总结

requests.get() 拿网页:status_code 看成败、resp.text 是源码、resp.encoding 管中文不乱码,timeout 防卡死 BeautifulSoup 对象树:find / find_all / get_text() / ["属性"]class_ 带下划线 CSS 选择器:select 一堆、select_one 一个,#id.classul.t li 后代选择 列表爬取套路:外层 select 拿条目,内层 select_one 取字段,strip=True 去空白 爬虫规矩:先看 robots.txt、time.sleep 控频、try/except 兜底,做个有礼貌的访客 完整闭环:请求 → 解析 → 提取 → 保存 CSV → 休息,爬虫入门达成

报错原因修复
ModuleNotFoundError: No module named 'requests'没安装 requests命令行执行 pip install requests beautifulsoup4
requests.exceptions.ConnectTimeout / ConnectionError连不上网站(断网、域名错误、被墙)检查网络,确认 URL 能正常访问,必要时配置代理
爬下来的中文全是乱码网页编码没设置对手动指定 resp.encoding = "utf-8"(或网页声明的其它编码)
AttributeError: 'NoneType' object has no attribute 'get_text'select_one 没匹配到内容,返回 None打印 soup.select(...) 检查页面结构是否改版,修正选择器
HTTPError 403 或频繁被限流网站识别出是爬虫User-Agent 请求头,降低频率,遵守 robots.txt
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇