零基础爬虫入门:从HTTP协议到Python实战,掌握核心请求与响应
1. 从“零”开始为什么学爬虫要先懂HTTP很多刚接触爬虫的朋友一上来就急着找教程照着代码敲requests.get()看到网页内容被打印出来就觉得“爬虫不过如此”。但很快你就会遇到各种奇奇怪怪的问题为什么有的网站能爬有的网站返回403为什么明明浏览器能打开代码却拿不到数据为什么登录后的页面爬不下来这些问题的根源十有八九都出在对HTTP协议的理解不够透彻上。HTTP协议全称是超文本传输协议它是互联网世界进行数据通信的基石。你可以把它想象成快递员和收件人之间的一套标准对话流程。你想从网站服务器获取一个页面包裹你的爬虫程序快递员就需要按照这套流程去敲门、说明来意、接收包裹。如果你不懂这套流程的规矩比如敲门声音太小请求头不对、说错了暗号Cookie缺失、或者拿包裹的姿势不对请求方法错误服务器这个“门卫”就会直接拒绝你或者给你一个空盒子。所以对于零基础的爬虫学习者来说跳过HTTP直接学库就像学开车不看交通规则短期内可能能把车开动但一旦上路处处是隐患。理解HTTP不仅能让你写出更稳定、更高效的爬虫更能让你具备独立分析和解决各种反爬问题的能力。这篇文章我们就从一个完全零基础的视角把HTTP协议里那些爬虫必须知道的事儿掰开揉碎了讲清楚。2. HTTP协议的核心请求与响应的“一问一答”HTTP协议的本质非常简单就是客户端比如你的浏览器或爬虫程序和服务器之间的一次“请求-响应”对话。一次完整的对话我们称之为一个HTTP事务。理解这个对话的结构是后续一切操作的基础。2.1 HTTP请求你的爬虫要“说”什么当你的爬虫想要获取一个网页时它需要向服务器发送一个结构化的请求。这个请求主要包含三部分请求行、请求头、请求体。请求行是请求的“开场白”它告诉服务器“我想干什么”。它由三部分组成请求方法最常见的是GET和POST。GET用于获取数据比如打开一个网页POST用于提交数据比如登录、发表评论。在地址栏直接输入网址就是一次GET请求。请求URL就是你想访问的资源地址比如https://www.example.com/page。HTTP版本目前主流是HTTP/1.1和HTTP/2。对于爬虫初学者知道我们通常用HTTP/1.1就够了。一个典型的请求行看起来像这样GET /page HTTP/1.1。请求头是请求的“附加说明”它包含了一系列键值对向服务器提供关于这次请求的更多元信息。对于爬虫来说以下几个头信息至关重要User-Agent这是你的“身份证”。它告诉服务器是什么客户端在访问。浏览器有浏览器的User-Agent比如Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36。如果你用Python的requests库而不设置这个头默认的User-Agent会是类似python-requests/2.28.1这等于直接告诉服务器“我是爬虫”很容易被拦截。所以伪装成一个常见的浏览器User-Agent是反反爬的第一步。Host指定要访问的服务器域名。这是HTTP/1.1必须的字段。Cookie这是维持会话状态的“令牌”。很多网站用Cookie来识别用户是否登录。当你用浏览器登录后服务器会给你一个Cookie之后浏览器每次请求都会带上它服务器就知道你是谁了。爬虫要访问需要登录的页面就必须在请求头里带上正确的Cookie。Referer这个头告诉服务器你是从哪个页面跳转过来的。有些网站会检查Referer如果发现你不是从它的站内链接跳转过来的比如直接访问某个详情页可能会拒绝服务。这在爬取图片、视频等资源时很常见。Content-Type当请求方法是POST并且需要提交数据如表单时这个头用来告诉服务器你提交的数据是什么格式的比如application/x-www-form-urlencoded标准表单格式或application/json。请求体并不是所有请求都有。GET请求通常没有请求体它的参数一般附加在URL后面比如/search?qpython。而POST请求通常有请求体里面装着要提交给服务器的数据比如你的用户名和密码。2.2 HTTP响应服务器“回”了什么服务器收到请求后会进行处理并返回一个响应。响应同样由三部分组成状态行、响应头、响应体。状态行是服务器的“第一句回应”它也包含三部分HTTP版本、状态码和状态描述。状态码是我们判断请求成败的关键必须熟记几个常见的200 OK成功这是最希望看到的表示请求成功响应体里就是你要的数据。301 Moved Permanently / 302 Found重定向。服务器告诉你你要的资源不在这个地址了新的地址在响应头的Location字段里。你的爬虫需要自动跟进这个新地址。403 Forbidden禁止访问。服务器理解你的请求但拒绝执行。常见原因包括IP被封、没有访问权限、User-Agent被识别为爬虫。404 Not Found未找到。请求的资源在服务器上不存在可能是URL拼写错误。500 Internal Server Error服务器内部错误。这是服务器端出了问题和你无关通常可以稍后重试。503 Service Unavailable服务不可用。服务器可能因为负载过高暂时无法处理请求这也是需要重试的信号。响应头和请求头类似包含服务器返回的元信息。对爬虫有用的包括Set-Cookie服务器通过这个头要求客户端你的爬虫保存Cookie用于后续的会话管理。你需要从响应头中提取Cookie并在后续的请求中带上。Content-Type告诉客户端响应体里的数据是什么格式。比如text/html是网页application/json是JSON数据image/jpeg是图片。你的爬虫要根据这个信息来决定如何解析数据。Location配合3xx状态码使用指明重定向的目标地址。响应体就是核心数据所在。对于网页爬虫响应体通常是HTML代码对于API爬虫可能是JSON或XML格式的数据。我们写爬虫的最终目标就是从响应体中提取出结构化的信息。3. 用Python实战手动“组装”一个HTTP请求理解了理论我们立刻用Python来实践。我们不直接用高级的requests库虽然它最终会简化一切而是先用更底层的socket库来手动模拟一次HTTP请求这能让你对“请求报文”有刻骨铭心的认识。假设我们要爬取http://httpbin.org/get这个测试网站它专门用于HTTP请求测试。以下代码展示了一个最原始的GET请求import socket # 1. 创建一个socket对象 client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 连接服务器 (httpbin.org 的IP和HTTP默认端口80) server_address (httpbin.org, 80) client_socket.connect(server_address) # 3. 手动组装HTTP请求报文 # 请求行 request_line GET /get HTTP/1.1\r\n # 请求头 request_headers Host: httpbin.org\r\n request_headers User-Agent: MySimpleCrawler/1.0\r\n request_headers Connection: close\r\n # 请求完成后关闭连接 # 空行分隔头和体 empty_line \r\n # GET请求没有请求体 # 拼接完整的请求报文 request_message request_line request_headers empty_line # 4. 发送请求需要编码成bytes client_socket.send(request_message.encode()) # 5. 接收响应 response_data b while True: chunk client_socket.recv(4096) if not chunk: break response_data chunk # 6. 关闭连接 client_socket.close() # 7. 解码并打印响应 response_text response_data.decode(utf-8, errorsignore) print(response_text)运行这段代码你会得到一长串输出。其中开头部分就是状态行HTTP/1.1 200 OK后面是各种响应头最后在响应体里你会看到httpbin.org把你发送的请求头信息以JSON格式原样返回了。这个过程清晰地展示了所谓的“发送一个HTTP请求”本质上就是通过TCP连接向服务器发送一段符合特定格式的文本字符串。注意现代很多网站都使用了HTTPS即HTTP over SSL/TLS这涉及加密连接用socket直接实现会更复杂。但原理不变只是传输层多了加密步骤。在实际爬虫中我们几乎不会直接操作socket但这次手动体验至关重要。4. 进阶工具Requests库如何简化一切手动组装请求太繁琐而且难以处理连接池、重试、编码、Cookie管理等复杂问题。因此我们使用requests库它是对HTTP协议的友好封装。上面socket代码的等效requests实现简单到令人发指import requests response requests.get(http://httpbin.org/get, headers{User-Agent: MySimpleCrawler/1.0}) print(response.status_code) # 打印状态码如 200 print(response.headers) # 打印响应头字典 print(response.text) # 打印响应体文本自动解码requests库帮我们自动完成了建立连接、组装报文、发送请求、接收响应、解码内容、管理连接等一系列工作。response对象包含了所有我们需要的信息。这才是我们日常爬虫应该使用的方式。4.1 关键对象与方法解析requests.get(url, paramsNone, **kwargs)发起GET请求。params参数可以接收一个字典库会自动将其转换为URL后的查询字符串例如params{key1: value1, key2: value2}会生成?key1value1key2value2并拼接到URL后。requests.post(url, dataNone, jsonNone, **kwargs)发起POST请求。提交表单数据用data参数字典或元组列表提交JSON数据用json参数字典。**kwargs这是关键它允许我们传入大量可选参数来控制请求最常用的有headers字典类型设置请求头。cookies字典类型设置Cookie。timeout设置请求超时时间秒避免程序永远等待。proxies字典类型设置代理IP格式如{http: http://10.10.1.10:3128, https: http://10.10.1.10:1080}。这是应对IP封锁的常用手段。Response对象status_codeHTTP状态码。headers响应头字典。text响应内容的字符串形式requests会基于响应头自动猜测编码有时会猜错。content响应内容的二进制形式。当text解码乱码时可以用content.decode(正确的编码如utf-8)。encodingrequests猜测的编码。你可以手动修改它例如response.encoding gbk然后再访问response.text就会用新编码解码。json()如果响应内容是JSON格式直接调用此方法可以解析成Python字典或列表无需再用json.loads()。cookies服务器通过Set-Cookie头设置的CookieJar对象。4.2 一个完整的带会话和错误处理的爬虫示例很多网站需要维持会话Session比如登录后的一系列操作。requests.Session()对象可以帮我们自动管理Cookie让我们像浏览器一样保持登录状态。import requests from requests.exceptions import RequestException, Timeout import time # 创建一个会话对象 session requests.Session() # 为本次会话设置统一的请求头 session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 }) try: # 示例1模拟登录假设是一个简单的登录接口 login_url https://example.com/login login_data { username: your_username, password: your_password } # 使用session.post登录成功后cookie会自动保存在session中 login_resp session.post(login_url, datalogin_data, timeout5) login_resp.raise_for_status() # 如果状态码不是200抛出HTTPError异常 print(f登录成功状态码{login_resp.status_code}) # 示例2访问登录后才能看的页面 profile_url https://example.com/dashboard # 这里不需要手动传递cookiesession会自动带上 profile_resp session.get(profile_url, timeout5) profile_resp.raise_for_status() # 检查编码并获取内容 if profile_resp.encoding is None or profile_resp.encoding.lower() iso-8859-1: # 如果requests猜的编码不对常见于中文网站可以手动指定 profile_resp.encoding utf-8 # 或 gbk, gb2312 html_content profile_resp.text # 这里可以开始用BeautifulSoup或lxml解析html_content print(成功获取到页面内容长度, len(html_content)) except Timeout: print(请求超时可能是网络问题或服务器响应慢。) except RequestException as e: print(f请求发生错误: {e}) finally: # 关闭会话虽然不是严格必须但是个好习惯 session.close()这个例子涵盖了会话管理、统一请求头设置、超时控制、异常处理、编码处理等爬虫中的常见实践。session对象极大地简化了状态管理。5. 爬虫工程师的必修课解码、重定向与超时掌握了基本请求后我们会遇到三个高频且必须处理好的问题字符编码、重定向和超时控制。处理不好它们爬虫的稳定性和数据准确性会大打折扣。5.1 字符编码告别乱码的噩梦网页编码不统一是爬虫的一大坑。requests的response.text属性会自动根据响应头中的charset或通过内容分析来猜测编码。但服务器返回的头部信息可能是错的或者根本没有charset信息。实战策略优先信任响应头首先查看response.encoding和response.headers.get(Content-Type)。手动检测与覆盖如果发现text属性输出是乱码一个可靠的方法是使用chardet库进行检测注意这需要额外安装pip install chardet。import requests import chardet resp requests.get(http://some-site-with-wrong-encoding.com) # 方法1使用chardet检测二进制内容的编码 raw_data resp.content detected_encoding chardet.detect(raw_data)[encoding] # 检测结果可能为None需要给个默认值 confidence chardet.detect(raw_data)[confidence] if confidence 0.7 and detected_encoding: # 置信度较高 resp.encoding detected_encoding else: resp.encoding utf-8 # 常见默认值或根据网站特点设为gbk correct_text resp.text print(correct_text[:500]) # 打印前500字符检查常见中文编码国内网站常见的编码是UTF-8和GBK或GB2312。对于已知的网站可以直接硬编码指定resp.encoding gbk。5.2 重定向处理跟着跳转走默认情况下requests会自动处理301302303307308这些重定向状态码。你最终得到的response.url是重定向后的最终地址response.history列表里则保存了所有重定向过程中的响应对象按发生顺序排列。但有时你需要禁用自动重定向比如为了分析跳转逻辑或者某些跳转需要携带特定的头信息如Referer。这时可以设置allow_redirectsFalse。resp requests.get(http://example.com/old-page, allow_redirectsFalse) if resp.status_code in [301, 302, 303, 307, 308]: redirect_url resp.headers[Location] print(f页面已重定向至: {redirect_url}) # 然后你可以手动构造一个新的请求去访问redirect_url并带上必要的headers5.3 超时控制给你的爬虫加上“保险丝”网络是不稳定的。一个请求可能因为对方服务器慢、网络拥堵而长时间没有响应。如果不设置超时你的爬虫线程可能会永远挂起。timeout参数是必须设置的。timeout可以是一个浮点数代表从发送请求到接收响应总时间的秒数。也可以是一个元组(connect_timeout, read_timeout)分别代表连接超时和读取超时。# 总超时5秒 try: resp requests.get(http://slow-server.com, timeout5) except requests.exceptions.Timeout: print(请求超时了) # 连接3秒超时读取10秒超时 try: resp requests.get(http://slow-server.com, timeout(3, 10)) except requests.exceptions.ConnectTimeout: print(连接服务器超时) except requests.exceptions.ReadTimeout: print(服务器响应太慢读取超时)设置一个合理的超时时间比如3-10秒并在异常捕获中实现重试逻辑是构建健壮爬虫的关键一环。你可以结合retrying库或自己写循环来实现简单的重试机制。6. 应对反爬理解HTTP层面的博弈网站为了防止被过度爬取会设置各种反爬虫机制。其中很多都基于HTTP协议的特征。作为爬虫开发者我们需要在HTTP层面进行“伪装”和“对抗”。6.1 User-Agent轮换与池化使用单一或明显的爬虫User-Agent是自投罗网。解决方案是准备一个User-Agent列表每次请求随机选取一个。这些User-Agent字符串可以从网上搜索“最新User-Agent大全”获得应涵盖主流浏览器Chrome, Firefox, Safari, Edge和不同操作系统版本。import random import requests USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, # ... 更多UA ] def get_with_random_ua(url): headers { User-Agent: random.choice(USER_AGENTS) } return requests.get(url, headersheaders)6.2 处理Cookie与Session对于需要登录的网站核心是获取并维持有效的Cookie。手动获取先用浏览器登录然后通过开发者工具F12 - Network - 找到任意请求 - Headers - Request Headers - cookie复制出Cookie字符串在requests中设置headers{Cookie: 复制的字符串}。这种方法简单但不持久Cookie会过期。模拟登录通过分析登录页面的表单用session.post()提交用户名和密码可能还有隐藏的token等字段。成功后session会自动管理后续请求的Cookie。这是更可靠和自动化的方法。注意Cookie的更新有些网站在交互过程中会更新Cookie。使用session对象可以自动处理。如果手动管理需要从Set-Cookie响应头中提取新的Cookie值并更新。6.3 设置Referer和Host头一些网站会检查Referer头确保请求来源于站内。例如直接访问一个图片链接可能返回403但如果Referer是站内的某个页面就能正常访问。在爬取这类资源时需要正确设置Referer。headers { User-Agent: ..., Referer: https://target-site.com/parent-page.html # 假设图片来自这个父页面 } resp requests.get(https://target-site.com/image.jpg, headersheaders)Host头在HTTP/1.1中是必须的requests库会自动帮你添加通常不需要手动设置除非你在进行一些非常规的代理或Host绑定操作。6.4 频率控制与代理IP即使伪装得再好过快的请求频率也会触发服务器的风控。控制爬取速度是基本的道德和技术要求。简单等待在请求间使用time.sleep(random.uniform(1, 3))加入随机延迟。更智能的控制使用time.perf_counter()记录上次请求时间确保间隔。当单一IP因频率过高被封锁时就需要使用代理IP。你可以使用付费代理服务或自建代理池。在requests中使用代理非常简单proxies { http: http://user:pass10.10.1.10:3128/, # 有密码的代理 https: http://10.10.1.10:1080, # 无密码的代理 } # 或者所有协议走同一个代理 # proxies {http: http://10.10.1.10:3128, https: http://10.10.1.10:3128} try: resp requests.get(http://example.com, proxiesproxies, timeout10) print(resp.status_code) except requests.exceptions.ProxyError: print(代理连接失败需要更换代理IP。)使用代理时务必做好错误处理因为代理IP很可能不稳定或失效。7. 调试与排查当爬虫不工作时怎么办你的爬虫突然拿不到数据了或者返回403/500错误该怎么办不要慌按照以下步骤进行HTTP层面的排查。第一步重现问题捕获原始信息使用最简化的代码复现问题并打印出最详细的请求和响应信息。import requests url https://problem-site.com/api/data # 1. 开启详细日志可选信息量大 # import logging # logging.basicConfig(levellogging.DEBUG) # 2. 发送请求并捕获异常 try: resp requests.get(url, timeout10) resp.raise_for_status() # 如果状态码不是2xx抛出异常 except requests.exceptions.HTTPError as e: print(fHTTP错误: {e}) if resp is not None: print(f状态码: {resp.status_code}) print(f响应头: {resp.headers}) # 有时错误信息在响应体中 print(f错误响应体: {resp.text[:500]}) except Exception as e: print(f其他错误: {e}) else: # 请求成功 print(请求成功) print(f最终URL: {resp.url}) # 检查是否有重定向 print(f编码: {resp.encoding}) print(f内容预览: {resp.text[:200]})第二步对比浏览器行为用浏览器Chrome/Firefox的开发者工具访问同一个URL。打开Network面板。清空记录然后访问或刷新目标页面。找到目标请求可能是XHR/Fetch或Doc类型点击查看Headers选项卡。仔细对比Request Headers你的爬虫的User-Agent和浏览器的一样吗浏览器请求带了哪些Cookie你的爬虫带了吗浏览器请求的Referer是什么需要模拟吗是否有特殊的头如Authorization、X-Requested-With等查看Payload或Request选项卡如果是POST请求浏览器提交了哪些表单数据或JSON数据你的爬虫模拟对了吗第三步模拟浏览器请求将你在浏览器中看到的所有关键头信息复制到你的爬虫代码中。headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, # 注意requests自动处理gzip不要设置这个头 Connection: keep-alive, Upgrade-Insecure-Requests: 1, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: none, Sec-Fetch-User: ?1, Cache-Control: max-age0, # 将从浏览器复制的Cookie字符串放在这里 Cookie: your_cookie_string_here } # 注意Accept-Encoding头通常由requests自动管理手动设置可能导致解压错误。 resp requests.get(url, headersheaders)提示requests库默认支持解压gzip和deflate编码的响应体所以通常不需要也不应该在请求头中设置Accept-Encoding: gzip库会自动处理。手动设置反而可能引发问题。第四步检查动态内容与JavaScript如果以上步骤都做了还是拿不到数据或者拿到的HTML里没有你想要的数据只有一些JavaScript框架代码那么目标数据很可能是通过JavaScript动态加载的。这时简单的HTTP请求就无能为力了你需要使用Selenium、Playwright或Pyppeteer这类能控制真实浏览器的工具来渲染页面或者更高级地去分析网页加载过程中的XHR/Fetch网络请求直接模拟那些请求来获取数据这又回到了HTTP协议本身。这超出了本文“零基础HTTP”的范围但它是爬虫工程师进阶的必经之路。排查过程的核心思想就是“对比”和“还原”让自己的爬虫请求尽可能接近浏览器的请求。理解了HTTP协议你就掌握了进行这种对比和还原的最基本、最重要的工具。