1 requests 与 Response 对象 —— 爬虫的第一步
是什么?为什么学?
requests 是 Python 最流行的 HTTP 请求库。爬虫的第一步永远是「把网页拿回来」:requests.get(url) 向服务器发请求,返回 Response 对象,里面装着状态码、网页文本、编码。所有爬虫和 API 调用都以它为起点。我们每天上网看到的网页,本质是一堆 HTML 文本。爬虫就是「用程序代替人去看网页」:先请求网页拿到 HTML,再从 HTML 里把想要的信息挑出来。
底层原理
requests.get(url) 内部:建立连接 → 发送请求头 → 服务器返回响应 → 封装成 Response。三个常用属性:resp.status_code(200 成功、404 不存在、403 被拒)、resp.text(网页源码)、resp.encoding(编码,不对中文就乱码)。**网络不可靠,要加 timeout 和 try/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 只取第一个;拿到 li 后 li.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、.class、ul.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 |