
刚开始学爬虫的时候我其实特别抗拒学网页基础这一块觉得不就是发个请求拿个页面吗代码跑通就完事了。后来真正动手抓数据遇到各种莫名其妙的问题——有的页面死活抓不到、有的抓到一堆乱码、有的请求发出去直接给你一个看不懂的数字——才意识到爬虫写不好问题八成不在代码上而是没搞懂网页工作最基本的规则URL、请求、响应、状态码。这四个词就是浏览器和网站服务器之间日常对话的四要素。爬虫说白了就是模拟浏览器去跟服务器要数据你要是不会说这门外语服务器要么不理你要么给你一堆你接不住的东西。所以这一节我们就把网页工作的底层逻辑吃透。不管你是想抓商品价格、采集新闻文章还是分析公开数据这些都是绕不开的地基。内容不难但我会把当初自己踩过的坑一并说出来帮你少走点弯路。1. 为什么爬虫的第一步是先读懂网页的工作方式很多零基础的读者一上来就想写代码问我能不能直接给一份完整的爬虫源码。我的回答永远是先别急。爬虫的本质不是写代码而是模拟浏览器和服务器对话。你把对话规则搞清楚了代码就是套模板的事反过来规则不懂哪怕给你十份源码你换个网站照样歇菜。1.1 爬虫与浏览器看起来一样做起来不同浏览器和爬虫做的事情本质上是一样的向服务器发起请求、接收响应、解析内容。唯一的区别是浏览器给用户看渲染后的页面爬虫拿的是原始HTML或数据文件。这就带出一个关键意识你在浏览器地址栏里看到的网址跟你用代码请求时发送的地址不完全是一回事。浏览器会自动帮你处理很多东西比如自动添加请求头、自动携带Cookie、自动解压内容、自动管理重定向。而用Python的requests库发请求时这些自动都要你自己来考虑。所以这一节的核心目标就是让你具备爬虫视角——看到一个网页能自动在脑海里拆解出它的URL结构、请求参数、可能遇到的响应状态。有了这个视角后面写代码才会有方向。1.2 网页请求到底经历了什么我打个比方。你在餐厅点菜你翻开菜单输入URL告诉服务员你要什么菜发送请求后厨做好端上来服务器返回响应服务员告诉你这道菜有没有、多少钱、要不要等状态码。如果菜没有了服务员会直接告诉你这个菜今天没有404如果没付款服务员会拦住你说请先买单401/403。整个过程就是一次HTTP事务。一次完整的网页访问一定会经历四件事解析URL确定去哪找目标服务器构造请求明确要什么以及以什么身份要服务器处理后返回响应HTML、JSON、图片等都算状态码告诉你这次点菜是成功了、被拒绝了还是出错了。爬虫要做的就是把这四个环节全部自己掌控住四个环节里任何一个出问题爬虫都会运行不出内容。2. URL互联网的定位系统URLUniform Resource Locator翻译过来是统一资源定位符。你可以把它理解成互联网的门牌号。爬虫的第一步永远是从URL开始的——你要知道目标数据住在哪个地址才能去敲门。2.1 拆开一个URL看它到底说了什么我们拿一段真实URL来拆例如https://example.com/products/list?keywordpythonpage2#reviews这段地址分为几个部分每个部分都有明确职责组成示例作用协议https://用哪种规则通信常见有http和https后者有加密域名example.com服务器的名字需要通过DNS解析成IP端口443隐藏了服务器上的门牌号http默认80https默认443路径/products/list请求服务器上的哪个资源查询参数keywordpythonpage2以键值对形式传给服务器的额外条件锚点#reviews页面内部的定位标记不会发送给服务器在爬虫里最常打交道的其实是查询参数。比如翻页可能就靠page2、page3变化搜索商品可能靠keyword变化筛选价格区间可能靠min_price和max_price变化。实操中你必须要记住的一点有时候同一个页面URL里参数很多一眼看去全是无规律的数字和字母比如热搜词里出现的dps://p?urlhttps%3a%2f%2f...这类带大量转义字符的地址。别慌多数看起来复杂的URL只是把真实参数做了一次URL编码后面细说。你在浏览器里看到的是一个地址复制出来又变成另一串这个现象特别容易让新手发懵。2.2 爬虫中URL的坑编码、拼接、变动第一个坑URL编码。URL里不能直接出现中文、空格、#、 等特殊字符所以浏览器会用百分号编码Percent-encoding把它们替换掉。比如中文爬虫会被编码成%E7%88%AC%E8%99%AB。热搜词里那一长串https%3a%2f%2f...其实就是把https://里的冒号和斜杠做了编码。所以你在代码里拼URL时最好用requests.utils.quote()统一编码参数而不是手动拼接字符串否则很容易拼出非法URL。我用一个简单示例import requests from urllib.parse import urlencode params { keyword: Python爬虫, page: 1 } # urlencode会自动把中文转成百分号编码避免非法字符 base_url https://example.com/search final_url base_url ? urlencode(params) print(final_url) # 输出https://example.com/search?keyword%E7%88%AC%E8%99%ABpage1第二个坑URL拼接。有些人喜欢用字符串的来拼URL一遇到参数里有或?就把整个地址搞坏。正确做法是用params参数让requests自己拼接或者用urlencode统一处理。记住不要手动拼接Query String这是新手最容易犯的错之一。第三个坑URL会变动。很多网站现在用动态加载你看到的URL可能是JavaScript在运行过程中根据用户操作临时生成的。这时候你在网页上复制到的链接和真正请求数据时用的链接不是同一个。遇到这种情况得打开浏览器开发者工具F12里的Network面板看实际发出的网络请求是什么而不是盯着地址栏看。这是一个非常重要的爬虫排查意识。3. 请求客户端对服务器说我要什么URL知道了下一步就是发请求。HTTP请求就是客户端浏览器或爬虫按照约定格式向服务器发送一段问话。这段问话里藏着你的身份、你的需求、你希望拿到什么格式的结果。3.1 HTTP请求的四个组成部分一个完整的HTTP请求由请求行、请求头、空行、请求体四部分组成。请求行包含请求方法、请求路径、HTTP版本。比如GET /products/list?keywordpython HTTP/1.1。请求头Headers包含一堆元信息比如User-Agent你是什么客户端、Accept你能接收什么类型的数据、Cookie你的登录凭证、Referer你从哪个页面跳过来的。空行固定存在分隔请求头和请求体。请求体Body部分请求会在Body中携带数据比如POST提交表单时用户名密码都在体里。爬虫中请求方法是最基础的概念。最常见的两种是GET和POST。GET表示我想获取资源参数拼在URL上POST表示我想提交数据然后服务器返回结果参数放在请求体里。你可以理解成GET是查菜单POST是下订单。3.2 用requests发第一个请求Python里最常用的HTTP库是requests它把底层的HTTP协议封装得极其友好。来看最基础的一个GET请求import requests url https://example.com response requests.get(url) print(response.status_code) # 状态码200表示成功 print(response.text) # 服务器返回的文本内容这三行就是爬虫的最核心骨架。但注意真实爬虫不会这么裸奔至少要伪装请求头否则很多网站直接拒绝你import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 } url https://example.com response requests.get(url, headersheaders) print(response.status_code) print(response.text[:500]) # 打印前500个字符先确认内容对不对这里必须专门强调User-Agent这个请求头。热搜词里反复出现爬虫屏蔽垃圾爬虫这些词网站管理员经常通过User-Agent来判断访问者是不是脚本。很多请求发出去被拒都是因为默认的User-Agent比如python-requests/2.x暴露了爬虫身份。老老实实把它伪装成浏览器的值成功率会立刻高很多。3.3 请求头伪装成浏览器的基础操作除了User-Agent还有几个请求头在爬虫实战里经常要用到Referer标识请求来源页面。有些网站做防盗链要求图片或数据的请求必须带上正确的Referer否则返回403。Cookie很多内容是登录后才能看到的你需要先登录把Cookie复制到爬虫请求头里。Accept-Language告诉服务器要什么语言的版本有时候会影响返回内容。X-Requested-With不少网站靠这个判断是不是Ajax异步请求带上了它返回的数据往往是JSON而非HTML。组合起来一个相对完整的请求头是这样的headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36, Referer: https://example.com/, Accept-Language: zh-CN,zh;q0.9, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8 }设置这么多头不是没事找事而是为了让服务器觉得你是一个真实用户。服务器端常见的反爬手段就是检查请求头是否完整、是否符合真人浏览器特征。当然请求头不是越多越好关键是跟目标网站的浏览器行为保持一致。4. 响应服务器返回的菜单请求发出去服务器处理完就会返回响应。响应是爬虫要吃的饭不会看响应就不知道该怎么解析数据。4.1 响应里有什么HTTP响应同样由几部分组成状态行比如HTTP/1.1 200 OK直接告诉你本次请求结果响应头包含服务器类型、返回内容类型Content-Type、编码方式、Cookie设置等空行响应体服务器真正返回的数据可能是HTML、JSON、图片二进制流、CSS文件等。在requests库里response.text拿到的是解码后的文本response.content拿到的是原始字节流适合下载图片这类二进制文件response.json()可以直接把JSON字符串转成Python字典。最需要留意的是Content-Type响应头。它决定了响应体是什么类型text/html普通HTML页面application/jsonJSON数据现在很多网站用Ajax加载数据这种返回非常多image/jpeg、application/zip二进制文件。新手经常犯的错是请求一个JSON接口但拿response.text去打印看到一堆没有格式的文本以为出bug了其实只需要response.json()就能得到结构化数据。4.2 编码问题中文乱码的根源热搜词里有一条ajax请求设置编码格式这绝对是爬虫新手遇得最多的问题之一。中文乱码十有八九是编码判断错了。服务器返回的HTML页面里通常会通过meta charsetutf-8或响应头里的charset声明编码。但有些网站不声明或者声明得和实际内容不一致这时requests根据猜测去解码就容易出错。推荐的做法是先看响应头里的编码再看页面meta声明实在不确定就用response.content先拿到原始字节再手动指定编码解码response.encoding utf-8 # 手动指定编码 print(response.text) # 或者直接用原始字节解码 html response.content.decode(utf-8, errorsignore)注意普通网站现在大多都用UTF-8但一些老网站可能用GBK或GB2312。遇到中文乱码优先检查编码不要急着去改正则表达式。不是代码错了是解码方式不对。4.3 重定向为什么你请求的是A得到的是B还有一类响应行为非常隐蔽重定向。当你访问一个URL时服务器可能返回3xx状态码并告诉你你要的内容已经不在这了去新地址吧。浏览器会自动跳转爬虫的requests库默认也会自动跟随重定向所以你感觉不到。但有些场景你必须关掉自动重定向自己处理。比如某些短链接服务你需要知道最终的跳转地址是什么或者某些网站用重定向做跟踪统计你跟着跟着就丢了当前的会话状态。这时候用response requests.get(url, allow_redirectsFalse) # 关闭自动重定向然后从响应头的Location字段里读取新地址new_url response.headers.get(Location)在爬取一些需要模拟用户流程的网站时理解重定向非常关键你以为自己在请求页面A实际上服务器返回的是302到页面B而B是登录页或者异常提示页抓回来的HTML自然不是你想要的数据。5. 状态码服务器给你的暗号状态码是HTTP响应里最有代表性的三个数字它直接告诉你本次请求的结果。我把状态码比作服务员反馈给你的一句话三个数字就能让你快速判断菜做好了卖完了还是你没资格吃5.1 状态码分类速查状态码范围类别含义常见例子1xx信息提示请求已接收继续处理少见2xx成功请求处理成功200 OK3xx重定向需要进一步操作301永久重定向、302临时重定向4xx客户端错误请求方姿势不对400、401、403、4045xx服务端错误服务器出问题了500、502、503、504爬虫里最舒服的当然是200拿到200多半就可以开心地解析数据了。但我得提醒一句200不代表一切正常。有些网站即使校验失败、返回错误提示也会用200包装比如你请求一个需要登录的页面服务器返回200但内容是请先登录。所以拿到响应后不仅要看状态码还要看内容里是否包含你预期的数据。5.2 爬虫中常见的状态码与应对方案403 Forbidden服务器理解你的请求但拒绝执行。这是爬虫最常撞的墙通常是反爬机制生效了比如请求头不够真实、IP被临时限制、缺少Cookie等。应对思路完善请求头、降低请求频率、使用代理IP池、模拟真实用户行为。404 Not Found请求的资源不存在。如果你确认URL没写错那可能是资源被删了、路径变了或者参数给错了。热搜词里就有服务器响应显示您没有权限下载此文件这类场景本质是404或403。401 Unauthorized未认证。服务器说我不知道你是谁需要提供有效的身份凭证。热搜词里那句unexpected status 401 unauthorized: authentication fails就是典型场景。爬虫里遇到401多半需要登录后带上Token或Cookie再请求。429 Too Many Requests请求太频繁被限流。热搜词里的您最近作出的请求太多了。请稍候然后重试就是这个状态码的典型提示。这算是对爬虫的软性警告背后是服务器在按频率限制你的访问。遇到这种情况最好在代码里加入随机延时比如time.sleep(random.uniform(1, 3))调整抓取频率做个懂礼貌的爬虫。502 Bad Gateway / 504 Gateway Timeout服务端网关错误。热搜词里二次出现502这类状态码说明问题在服务器那一边不一定是爬虫的锅。可能是目标网站负载太高、代理层配置错误或者服务器暂时不可用。遇到5xx状态不要死磕简单time.sleep几秒后重试大概率就能恢复。应对思路总结成一段经验状态码是服务器给你的诊断信号别看见非200就慌。先根据码值判断问题出在客户端还是服务端4xx多检查自己的请求5xx多考虑目标网站的状态。然后针对性调整而不是盲目改代码。6. 爬虫实战中的常见问题与排查实录讲完理论来点真实的排障经验。这一部分都是我在实际写爬虫时遇到过的坑你迟早也会碰上。6.1 页面请求失败连错误提示都看不懂有次我抓一个电商的搜索接口控制台报错信息五花八门后来发现核心原因是没有带上完整的请求头。那个网站会先发一个预处理请求检查浏览器的指纹信息我用requests直接请求被判定为异常流量。解决办法用浏览器开发者工具F12的Network面板找到真实请求把所有请求头原样复制过来逐个比对很快就能定位是哪个头在起作用。排查思路打开浏览器F12切到Network面板。在页面上执行一次正常操作找到对应的网络请求。右键复制为cURL格式里面包含了完整的请求头、参数、Cookie。用requests库逐步还原测试哪个字段是必须的。这个方法能解决90%的请求被拒绝问题建议收藏。6.2 明明返回200却没有我需要的数据这个坑最深。我遇到过返回200、HTML代码也完整但我要的商品价格就是不在里面。后来才发现数据不是服务端直接渲染到HTML里的而是页面加载后用JavaScript通过Ajax请求接口拿到的。**热搜词里的f12无法加载响应数据:没有可用于预检**其实就是这个场景的另一种表现——浏览器要先发一个预检请求OPTIONS跨域请求没配好就导致后续请求发不出去。应对方式打开Network面板筛选XHR或Fetch类型的请求找到真正返回数据JSON的那个接口直接爬那个接口而不是爬页面HTML。这项工作叫接口分析是爬虫进阶最重要的技能之一。6.3 睡眠延时都被封究竟怎么限速爬虫高频请求一定会被封。热搜词里您最近作出的请求太多了就是服务器明确告诉你被限流了。单线程加固定时间间隔也未必安全因为固定间隔反而像机器行为。我的经验是加随机延时import time import random # 每次请求后随机休息模拟真实用户操作 time.sleep(random.uniform(0.5, 2.5))另外如果目标网站风控特别严格光靠延时不够。可以考虑用代理IP轮换、维护登录Cookie池、降低并发数。核心原则是把爬虫的请求特征做得像人而不是像一台高并发压力测试机。6.4 内容乱码、响应不完整先别怀疑人生乱码问题前面说过先检查编码。但还有一种情况是内容被压缩了。大型网站常用gzip或br压缩来减小传输体积requests库默认会处理gzip但有些压缩格式需要你自己解压。如果response.text打印出来是乱码而response.content看起来像二进制流很可能是压缩格式没处理好。这时候可以import gzip if response.content[:2] b\x1f\x8b: # gzip魔数 content gzip.decompress(response.content)响应不完整还有一个常见原因分块传输Chunked Transfer。数据被切成多块传输连接可能中途断开导致拿到的HTML不完整。遇到这种问题可以尝试增加超时时间或者用Session保持连接多次请求稳定性更高。7. 结语把网页当做一个系统去理解写爬虫这几年我最大的感受是爬到数据是结果理解系统才是能力。每次遇到反爬、封禁、数据异常本质上都是一次对网页工作方式理解的检验。URL相当于地址请求是问话响应是回答状态码是语气——把这四件事刻在脑子里再复杂的网站也能拆解出清晰的爬取路径。最后再分享一个小技巧遇到任何拿不准的请求先在浏览器里手动访问一遍同时开着F12观察真实请求长什么样再回到代码里复现。这比对着文档猜要高效十倍。下一节我们会开始写第一个真正意义上的爬虫程序把这节的基础全部用上到时候你会发现地基打得越扎实跑起来越稳。