上一章,我们把客户端和服务器这层关系讲清楚了。
这一章,就正式开始写代码。
很多人学网络请求,最容易卡住的地方不是代码太难,而是脑子里总觉得这件事很虚。 其实真正写起来,第一步并没有那么复杂。
你先别想着接口、登录、Cookie、反爬这些后面的东西。 先把最基础的一件事做明白:
用 Python 发一个请求,把网页内容拿回来。
而在 Python 里,做这件事最常见、最顺手的工具之一,就是 requests 库。它的官方文档把它作为一个简单的 HTTP 库来介绍;安装方式是 python -m pip install requests,最基础的用法就是 requests.get() 发请求,并得到一个 Response 对象。(requests.readthedocs.io[1])
一、为什么很多人一学爬虫,第一步都是先学 requests
因为你要抓网页,前提是先把网页拿回来。
浏览器能打开网页,是因为浏览器会发请求。 那 Python 程序想拿网页,本质上也是一样的逻辑:
发请求 拿响应 再处理响应内容
requests 的价值,就在于它把这件事做得很直白。
你不用先碰底层 socket,也不用一开始就理解特别复杂的网络细节。 很多最常见的请求操作,它都已经帮你封装好了。
所以对初学者来说,requests 最适合作为网络请求的第一站。
它不是让你绕开原理。 而是让你先把最核心的动作做起来。
二、先安装 requests
如果你的环境里还没有这个库,先安装:
python -m pip install requests
这是官方文档给出的安装方式。(requests.readthedocs.io[2])
安装好之后,就可以直接在代码里导入:
import requests
如果没有报错,通常就说明已经能用了。
这一小步看起来普通,但它意味着一件事:
从这一章开始,你就不只是写本地程序了。 你的代码,已经开始具备向外部服务器发请求的能力。
三、发出第一个请求,其实只要一行代码
先看最简单的例子。
import requestsresponse = requests.get("https://www.example.com")print(response)
这段代码做的事情非常直接。
requests.get(...) 表示发起一个 GET 请求。 服务器返回的结果,会被放进 response 这个变量里。
官方 Quickstart 里也是用 requests.get() 举例,并说明返回值是一个 Response 对象。(requests.readthedocs.io[3])
如果你运行这段代码,通常会看到类似这样的输出:
<Response [200]>
先别急着问别的,先把这个结果看懂。
它表示: 请求发出去了,服务器回应了,而且这次回应的状态码是 200。
后面我们还会专门讲状态码。 你现在只要先记住最朴素的一层意思:
这不是网页内容本身。 这是一个响应对象的简要展示。
真正的内容,还在 response 这个对象里面。
四、response 到底是什么
这是你现在最该理解清楚的东西。
很多新手第一次写完请求,会以为:
我是不是已经拿到网页了
答案是: 拿到了,但还没直接显示出来。
因为 response 不是单纯的一段字符串。 它是一个响应对象,里面包含很多信息。
比如:
状态码 响应头 网页文本 二进制内容 JSON 数据 最终请求到的 URL
官方文档明确把 r = requests.get(...) 的返回值称为 Response 对象,并说明后续要用的各种信息,都从这个对象里拿。(requests.readthedocs.io[4])
你可以把它理解成一个装着“服务器回复内容”的盒子。 盒子里不只装正文,还装着很多附加信息。
所以后面你写 response.text、response.status_code、response.headers,本质上都是在从这个盒子里取不同内容。
五、真正把网页内容拿出来,最常用的是 text
如果你现在想看网页正文,最直接的方式就是:
import requestsresponse = requests.get("https://www.example.com")print(response.text)
response.text 表示把服务器返回的文本内容拿出来。
官方 Quickstart 也把 r.text 作为最基础的读取响应内容方式,并说明 Requests 会基于响应头去猜测编码,再用这个编码解码文本内容。(requests.readthedocs.io[5])
你运行之后,大概率会看到一大段 HTML。 比如会有:
<!doctype html><html><head>...
这就很关键了。
因为你此刻会第一次非常直观地感受到:
所谓抓网页,第一步其实不是把网页显示出来。 而是先把网页背后的原始文本拿到手。
浏览器拿到这些 HTML 后,会继续渲染、排版、执行脚本,所以你看到的是页面。 而 Python 现在只是把这些原始内容拿回来给你。
这就是爬虫第一步的本质。
六、为什么拿到的是 HTML,而不是你肉眼看到的网页
这是很多初学者都会有的疑问。
明明浏览器里看到的是一个排版完整的网页,为什么程序拿回来的是源码一样的东西。
原因很简单。
浏览器除了请求网页,还会继续做很多事:
解析 HTML 加载 CSS 执行 JavaScript 下载图片 组合成最终页面
而 requests 最基础做的,是帮你把服务器返回的响应内容拿回来。 它不会帮你渲染成可视化网页。
所以你通过 response.text 看到的,通常是网页文本源码。 这不是拿错了。 这恰恰说明你已经拿到“网页的原材料”了。
后面你要做的,就是从这些原材料里提取自己真正想要的数据。
七、状态码是什么,为什么它特别重要
刚才你看到过:
<Response [200]>
这里的 200,其实就是状态码。
更规范一点的取法是:
import requestsresponse = requests.get("https://www.example.com")print(response.status_code)
官方文档把 r.status_code 作为最直接的状态码读取方式。(requests.readthedocs.io[6])
状态码可以简单理解成: 服务器对这次请求的处理结果编号。
最常见的感受层面,你先记这几个意思就够了:
200,通常表示请求成功 404,通常表示资源没找到 500,通常表示服务器内部出错
你现在不用背整套状态码体系,但一定要养成一个习惯:
写请求时,不要只看有没有内容。 先看状态码。
因为很多时候,程序表面没报错,不代表请求就是成功的。 可能服务器只是回了一个错误页面而已。
八、比起只看状态码,更稳一点的写法是 raise_for_status
很多初学者会写成:
import requestsresponse = requests.get("https://www.example.com")print(response.status_code)print(response.text)
这当然没问题。
但如果你想让错误更早暴露出来,一个更稳的习惯是调用:
response.raise_for_status()
例如:
import requestsresponse = requests.get("https://www.example.com")response.raise_for_status()print(response.text)
官方文档说明,raise_for_status() 会在 4XX 或 5XX 这类不成功的响应上抛出 HTTPError;如果状态码正常,比如 200,它就什么也不做。(requests.readthedocs.io[7])
这个方法特别值得早养成习惯。
因为它的意义是:
如果这次请求根本没成功,就别假装继续往下处理。 先把错误暴露出来。
这会让你的脚本更稳,也更容易排查问题。
九、写网络请求时,最好别偷懒,timeout 要尽早养成习惯
这一点非常重要。
很多新手刚开始写请求时,往往只写:
response = requests.get(url)
这样虽然能跑,但有个潜在问题:
如果服务器一直不回应,你的程序可能会一直等下去。
官方文档对 timeout 的建议非常明确:几乎所有生产代码都应该在几乎所有请求里带上这个参数,否则程序可能无限期卡住。文档也说明,timeout 不是整个下载总时长上限,而是底层 socket 在指定秒数内没有收到任何字节时就触发超时异常。(requests.readthedocs.io[8])
所以更推荐的写法是:
import requestsresponse = requests.get("https://www.example.com", timeout=10)response.raise_for_status()print(response.text)
这里的意思是:
最多等 10 秒。 如果 10 秒内对方一直没给出响应,就报超时。
你现在写练习脚本时,可能还感觉不到它的重要性。 但以后做批量请求、做接口调用、做爬虫时,这个习惯会非常值钱。
十、一个更像正式代码的最小版本,通常长这样
如果把前面几步合在一起,一个很像样的最小模板通常是:
import requestsurl = "https://www.example.com"response = requests.get(url, timeout=10)response.raise_for_status()html = response.textprint(html[:500])
这段代码里,每一步都非常有意义。
先定义 URL 再发请求 加超时 检查状态 再拿正文 最后只打印前 500 个字符看看
为什么我这里建议先打印一部分,而不是整页全打出来。
因为真实网页内容往往很长。 你第一次调试时,更重要的是确认:
请求成功没有 拿回来的到底是不是你以为的内容 大概长什么样
所以先看一截,通常比一下子把整页刷满屏更清楚。
十一、response.text 和 response.content,有什么区别
这是一个特别常见的问题。
你最常用的是 response.text。 它适合处理文本内容,比如 HTML、普通文本、很多接口返回的文本型结果。
而 response.content 拿到的是字节内容,也就是原始二进制数据。
官方文档把 r.text 归为文本响应内容,把 r.content 归为二进制响应内容,并明确区分了两者的用途。(requests.readthedocs.io[9])
你可以这样理解:
text 更像“已经帮你解码后的可读文本”content 更像“更原始的字节数据”
例如抓网页正文时,通常更常用:
response.text
但如果你以后要下载图片、文件、压缩包,通常更常用:
response.content
这一章先把网页抓取打牢就行,所以你当前阶段重点记住 text 即可。 但你要知道,content 这个入口也很重要,后面下载资源时会经常用到。
十二、如果网页乱码了,先想到 encoding
虽然 Requests 会自动猜测响应编码,但现实里并不是每次都猜得完全准确。官方文档说明,它会基于 HTTP 头部推测编码;你也可以查看或手动修改 r.encoding,再配合 r.text 使用。(requests.readthedocs.io[10])
比如:
import requestsresponse = requests.get("https://www.example.com", timeout=10)response.raise_for_status()print(response.encoding)
如果你发现文本明显乱码,有时可以手动指定:
response.encoding = "utf-8"print(response.text)
这里先别把它想得太复杂。 你现在只要建立一个排查意识:
请求成功了 但是内容像乱码 那就看看是不是编码问题
这会比盲目怀疑网站封你了更靠谱。
十三、GET 请求不只是访问固定地址,还经常要带参数
很多网页和接口,并不是访问一个死地址就够了。 它们往往还要带参数。
比如搜索。
你在搜索框里输入 Python 教程,本质上就是把关键词作为参数发给服务器。
Requests 官方文档推荐用 params 参数来传 URL 查询参数,而不是手工拼接字符串;它也说明这些参数会被正确编码到 URL 后面。(requests.readthedocs.io[11])
例如:
import requestsurl = "https://httpbin.org/get"params = {"keyword": "python","page": 1}response = requests.get(url, params=params, timeout=10)response.raise_for_status()print(response.url)print(response.text)
这里的重点不是 httpbin 这个网站本身。 重点是你要看懂这种写法的意思:
地址是地址 参数是参数 让库帮你正确拼接
这是一个非常值得早养成的习惯。
十四、为什么不建议手动拼字符串凑参数
很多新手一开始会写成这样:
url = "https://example.com/search?keyword=python&page=1"
它不是一定不行。 但一旦参数变多,或者参数里带空格、中文、特殊字符,就容易出错。
而用 params 的好处就在于:
更清楚 更安全 编码交给库处理 后期维护也方便
所以你以后看到带参数的请求,最好先想到这种结构:
params = {"key": "value"}
再把它交给 requests.get(..., params=params)。
这会让你的代码更像正式代码,而不是一次性拼出来的临时脚本。
十五、抓网页时,先别急着解析,先把原始内容确认清楚
这个习惯特别关键。
很多人一上来就想: 怎么提标题 怎么提正文 怎么提链接
其实你更稳的顺序应该是:
先请求 先确认状态码 先看是不是目标网页 先看返回内容大概长什么样 再决定怎么解析
也就是说,别跳步。
例如:
import requestsurl = "https://www.example.com"response = requests.get(url, timeout=10)response.raise_for_status()html = response.textprint("状态码:", response.status_code)print("最终地址:", response.url)print("前 300 个字符:")print(html[:300])
先看到这些,再往下走,会清楚很多。
因为你后面解析失败,很多时候不是解析库的问题,而是你根本没拿到自己以为的那个页面。
十六、为什么有时请求成功了,拿回来的却不是你想要的内容
这个现象很常见。
比如状态码是 200,看起来成功了。 但拿回来的内容可能是:
跳转页面 验证码页 登录页 错误提示页 一个空壳 HTML
这也是为什么我前面一直强调:
别只看有没有响应。 还要看响应内容是不是你真正想要的。
这一点会在后面讲请求头、Cookie、反爬时越来越明显。 但你现在至少先建立这个意识:
200 不等于万事大吉。 还得看正文到底是什么。
这会让你后面少走很多弯路。
十七、response.headers 也很有用,但这一章先知道它是啥就够了
除了正文和状态码,响应头也是很重要的信息。
Requests 文档说明,r.headers 可以把服务器返回的响应头当作一个字典来访问,而且头字段名大小写不敏感。(requests.readthedocs.io[12])
例如:
import requestsresponse = requests.get("https://www.example.com", timeout=10)response.raise_for_status()print(response.headers)print(response.headers.get("content-type"))
这有什么用。
很简单。
有时你想知道: 服务器返回的是 HTML、JSON,还是图片 编码是什么 有没有压缩 有没有重定向痕迹
这些信息很多都藏在响应头里。
这一章你不需要把响应头研究很深。 但至少要知道:
响应不只有正文。 还有很多“关于正文的信息”。
而响应头,正是其中很重要的一部分。
十八、如果服务器返回的是 JSON,requests 还能直接帮你解码
虽然这一章主题是抓网页,但你最好顺手知道这件事。
Requests 官方文档提供了 r.json() 作为 JSON 响应的解码方式;同时也明确提醒,r.json() 能成功解码,不代表这个 HTTP 请求就一定成功,真正判断成功与否还要看状态码或 raise_for_status()。(requests.readthedocs.io[13])
例如:
import requestsresponse = requests.get("https://httpbin.org/get", timeout=10)response.raise_for_status()data = response.json()print(type(data))print(data)
这一步你现在不用大量使用。 但你要先有一个整体印象:
抓网页时,经常拿到 HTML 调接口时,经常拿到 JSON
而 Requests 对这两类返回内容,都已经提供了很顺手的入口。
这也是它为什么特别适合初学者。
十九、POST 请求现在先有个印象就够了
前面我们一直在讲 GET,因为它最适合做“拿网页、拿页面数据”的入门场景。
但 Requests 官方文档同样给出了 requests.post() 的基础写法,还说明表单型数据可以通过 data= 发送,JSON 数据可以通过 json= 发送。(requests.readthedocs.io[14])
例如最基础的 POST 形式可以长这样:
import requestsurl = "https://httpbin.org/post"data = {"username": "tom","password": "123456"}response = requests.post(url, data=data, timeout=10)response.raise_for_status()print(response.text)
这一章你不用把 POST 挖很深。 但至少要知道:
GET 更像取数据 POST 更像提交数据
后面讲表单提交、登录、接口调用时,POST 会越来越常见。
二十、真正写 requests 时,异常处理也要有点概念
官方文档说明,网络层面的问题可能抛出 ConnectionError,超时会抛出 Timeout,HTTP 状态异常可以通过 raise_for_status() 触发 HTTPError,而这些异常都继承自 requests.exceptions.RequestException。(requests.readthedocs.io[15])
所以更稳一点的写法,通常会配合 try...except:
import requestsurl = "https://www.example.com"try: response = requests.get(url, timeout=10) response.raise_for_status() print(response.text[:300])except requests.exceptions.Timeout: print("请求超时")except requests.exceptions.RequestException as e: print("请求失败:", e)
这一段你现在不用背得滚瓜烂熟。 但最好先知道:
网络请求和本地字符串处理不一样。 它会受到网络、服务器、权限、超时等很多外部因素影响。
所以它天然就更适合写得稳一点。
二十一、写一个真正像样的网页抓取入门脚本
现在把前面最基础、最实用的东西合起来,写一个完整一点的版本:
import requestsdeffetch_webpage(url):try: response = requests.get(url, timeout=10) response.raise_for_status() print("请求成功") print("状态码:", response.status_code) print("最终地址:", response.url) print("响应头中的内容类型:", response.headers.get("content-type")) print("网页前 500 个字符:") print(response.text[:500])except requests.exceptions.Timeout: print("请求超时")except requests.exceptions.RequestException as e: print("请求失败:", e)fetch_webpage("https://www.example.com")
这段代码很值得你反复看。
因为它已经具备了一个最小但完整的抓网页流程:
发请求 设超时 检查状态 读取正文 查看响应头 做异常处理
这已经不是“玩具代码”了。 它已经是很像正式项目里最小网络请求模块的样子。
二十二、你现在最该形成的,不是 API 记忆,而是请求思维
学到这里,很多人容易把重点放歪:
get 怎么写post 怎么写params 怎么写text 和 content 怎么用
这些当然都要会。 但更重要的是,你脑子里有没有形成这样一条顺序:
先有 URL 再发请求 拿到 Response 先看状态 再看内容 最后决定怎么处理
如果这条顺序清楚了,后面你学请求头、Session、Cookie、HTML 解析时,都会轻松很多。
因为你已经知道自己现在处在哪一步。
二十三、这一章最容易踩的坑有哪些
第一个坑,是拿到 response 就以为拿到正文了。
其实真正网页内容通常要去看 response.text。
第二个坑,是只顾着打印正文,不看状态码。
请求失败时,你可能拿到的是错误页,而不是目标页面。
第三个坑,是不设 timeout。
程序一旦卡住,你会很难排查到底是网络慢,还是服务器没回。
第四个坑,是把带参数的 URL 全部手写拼接。
刚开始看着省事,后面会越来越乱。
第五个坑,是没有异常处理意识。
网络请求不是本地字符串操作,它天然更容易受外部环境影响。
这些坑并不高级,但几乎每个初学者都会踩。 早点知道,后面会顺很多。
二十四、这一章真正要带走的东西
到这里,你应该已经能清楚感受到:
所谓 requests 入门,不是学一堆函数名。 而是先把最基本的抓网页流程做通。
你要知道怎么安装。 你要知道怎么发 GET 请求。 你要知道返回的是 Response 对象。 你要知道正文通常看 text。 你要知道状态码要检查。 你要知道超时和异常处理不能完全不管。 你还要知道,参数、响应头、JSON 这些能力,后面都会慢慢接上。
只要这些基础顺了,后面的网页解析、接口抓取、反爬处理,才有地方落脚。
本章小结
requests 是 Python 里非常常用的 HTTP 请求库,官方文档给出的安装方式是 python -m pip install requests,最基础的请求写法是 requests.get(),返回值是 Response 对象。(requests.readthedocs.io[16])
这一章最重要的几个点,你要真正吃透:
抓网页的第一步,不是解析,而是先把网页内容请求回来。response.text 适合读取文本响应内容,response.content 更适合处理原始二进制内容。r.json() 可以直接解码 JSON 响应。(requests.readthedocs.io[17])response.status_code 可以查看状态码,raise_for_status() 可以在不成功的响应上主动抛错。(requests.readthedocs.io[18]) 带查询参数的 GET 请求,优先使用 params=。自定义请求头可以通过 headers= 传入。(requests.readthedocs.io[19]) 请求时最好显式设置 timeout,否则程序可能一直卡住;网络错误、超时、HTTP 状态异常都应该有基本的异常处理意识。(requests.readthedocs.io[20])
当你能把这些最基础的动作做顺,Python 就已经开始真正和外部网站、接口、服务器发生连接了。
课后练习
第一,自己安装 requests,然后访问一个公开网页,把状态码和前 300 个字符打印出来。
第二,试着请求一个不存在的页面,观察状态码和 raise_for_status() 的效果。
第三,找一个带搜索参数的网址,用 params= 的方式拼出请求,并打印最终 URL。
第四,分别打印 response.text 和 response.content,观察它们的区别。
第五,给自己的请求加上 timeout,再用 try...except 把超时和请求异常接住。