拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Requests与BeautifulSoup实战:Python爬虫入门到常见问题排查

1. 从零开始认识Requests和BeautifulSoup很多人第一次接触Python爬虫就是奔着Requests和BeautifulSoup这两个库去的。我在刚学爬虫的时候也被各种框架、scrapy、selenium劝退过但回过头来看用Requests发请求、用BeautifulSoup解析HTML才是最适合新手入门、也最符合只想要数据这个核心诉求的组合。简单说Requests负责把网页内容拿回来BeautifulSoup负责从拿回来的HTML里把需要的数据挑出来两者配合就是一套完整的抓取流程。这套方案能解决什么问题如果你的目标是抓取某个网页上的标题、链接、表格、列表数据并且网页本身没有复杂的JavaScript渲染逻辑那Requests加BeautifulSoup足够用了。它不依赖浏览器不需要处理自动化窗口也不用像scrapy那样建工程、配管道几行代码就能跑通第一个爬虫。适合刚入门Python、想快速理解HTTP请求和HTML解析的同学也适合想给公司内部做个简单数据采集工具但不希望引入过重框架的人。我见过不少初学者一上来就追最新的大模型逆向爬虫技术结果被各种加密参数、JS混淆劝退。实际上大部分常规数据抓取场景都用不上那些高级技术把Requests和BeautifulSoup吃透已经能覆盖日常工作里八成的需求了。这篇文章我就把完整的入门路径拆开讲包括HTTP基础、库的安装使用、HTML解析思路、一个可直接运行的完整案例以及我踩过的坑和排查方法。2. 为什么Requests加BeautifulSoup是最佳入门组合2.1 Requests库的核心地位帮你搞定HTTP请求爬虫的第一步是获取网页内容这就绕不开HTTP协议。HTTP协议本质上就是客户端和服务器之间的一种对话规则客户端说我要某个资源服务器返回这是你要的资源或者出错啦。Requests库做的事情就是用Python把这种对话包装得非常简单。在Requests出现之前Python标准库里的urllib用起来很别扭设置headers、处理cookie、管理代理都要写不少代码。Requests的口号是HTTP for Humans它把GET、POST、HEAD这些请求方法封装成同名函数开发者只需要关心请求参数和响应内容。比如请求一个网页最核心的代码就是requests.get(url)返回的response对象里response.text是HTML文本response.status_code是状态码response.headers是响应头。这里要补一句很重要的认知浏览器访问网页时服务器能看到你的User-Agent、Referer、Cookie这些信息。很多网站会检查User-Agent如果发现是爬虫工具比如Python-requests的默认UA很可能直接拒绝响应。所以入门第一天就应该养成设置headers的习惯。这是我踩过的最早的坑之一后面在案例里我会给出完整的headers配置。2.2 BeautifulSoup的解析哲学像用选择器一样找数据网页拿到手之后是一大堆HTML标签。BeautifulSoup的作用就是把这堆标签变成一棵可操作的树然后通过标签名、class、id、属性等条件定位到目标元素。它的名字来源于汤的概念——HTML再混乱BeautifulSoup也能帮你炖成一碗干净的汤。BeautifulSoup支持多种解析器最常用的是html.parserPython自带无需额外安装和lxml速度更快需要安装。对于入门阶段html.parser完全够用如果解析速度有明显瓶颈再换lxml不迟。用BeautifulSoup定位元素有几种方式find()和find_all()是最基础的方法分别返回第一个匹配节点和所有匹配节点select()则支持CSS选择器语法比如select(div.news-item h2 a)。我个人觉得CSS选择器更直观尤其是熟悉前端的人上手特别快。不过find_all配合各种参数也有它的灵活性两者互补。提示解析HTML时容错很重要。真实网页不像教科书那么规整标签缺失、属性值里有空格、动态拼接的class都是常态。BeautifulSoup恰好擅长处理这种不完美的HTML这也是它比正则表达式更适合做HTML解析的根本原因。2.3 为什么不建议入门就上scrapy或selenium很多新手看到招聘要求里写scrapy就觉得应该从scrapy学起。但scrapy是一个完整的爬虫框架它有自己的项目结构、spider、item、pipeline、middleware体系学习曲线陡峭。用它抓一个页面你得先建工程、写配置、理解异步机制这套流程对刚入门的人来说负担很重。用Requests加BeautifulSoup十行代码就能看到效果正反馈快适合建立信心。selenium解决的是另一个问题——JavaScript动态渲染。但selenium要驱动真实浏览器资源开销大速度慢而且很多网站的登录逻辑、验证码逻辑会让浏览器自动化变得很脆弱。如果目标网站不需要JS渲染用selenium就是杀鸡用牛刀。正确的策略是先用Requests把页面拿下来看看内容里有没有需要的数据如果没有再考虑动态渲染方案。3. 环境准备从Python安装到Requests和BeautifulSoup配置3.1 Python环境与pip安装常见坑开始写代码之前先把环境搞定。Python的安装本身不难但有几个细节会影响后续体验。下载Python时建议到官网装的时候勾选Add Python to PATH这样命令行里才能直接用python命令。我见过好多同学装完Python之后在cmd里敲python提示找不到命令基本都是因为没勾这个选项。验证安装是否成功打开终端Windows的cmd或PowerShellmacOS的Terminal输入python --version如果输出了Python版本号说明环境正常。接着安装Requests和BeautifulSoup。BeautifulSoup的包名是beautifulsoup4注意不是bs4——虽然导入的时候用的是from bs4 import BeautifulSoup但安装命令必须是beautifulsoup4。这是一个典型的入门坑我当初就在这里栽过跟头。pip install requests beautifulsoup4如果pip下载速度慢可以用国内镜像源。这里插一句有人会问pip install django pymodbus requests这种多包同时安装是怎么回事其实pip支持一次性安装多个包用空格分隔包名就行。但日常使用还是建议按需安装避免装了一堆用不上的包。注意如果你同时装了Python2和Python3或者系统对Python有版本管理记得用pip3而不是pip。Windows用户还要注意某些环境变量冲突会导致pip指向错误版本这时候用python -m pip install xxx最保险。3.2 VSCode配置Python开发环境编辑器方面我个人推荐VSCode免费、插件生态好、调试方便。VSCode装好之后去扩展市场搜索Python扩展微软官方出的那个装上之后就能享受语法高亮、代码补全、错误提示。VSCode里跑Python脚本有几种方式可以直接右键代码文件选择Run Python File in Terminal也可以在终端里手动执行python 文件名.py。调试时建议用VSCode自带的调试器打断点观察变量非常方便——尤其适合爬虫这种需要反复查看中间结果的场景。我日常的习惯是先用VSCode写代码用调试模式跑看到response内容正常后再回到终端正式执行。爬虫开发的核心循环就是发送请求、打印响应、调整代码、再发送这个迭代过程调试工具的好坏直接影响效率。3.3 第一个测试请求确认Requests能正常工作环境配好之后先跑一个最简单的测试脚本验证Requests真的能拿到数据import requests url https://httpbin.org/get # 这是一个专门用于测试HTTP请求的公开服务 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } response requests.get(url, headersheaders, timeout10) print(response.status_code) print(response.text)httpbin.org是一个专门给开发者测试HTTP请求的网站返回的内容里会包含你发送的请求头信息。如果status_code是200且返回的JSON里能看到你设置的User-Agent说明Requests工作正常网络也通。timeout10这个参数我第一次没加结果程序卡住不动了最后才发现是请求超时但没设置上限。养成设置timeout的习惯能避免爬虫因为一个无响应的URL而永远卡死。这是入门第一天就该养成的安全意识。4. 核心实操用Requests抓取网页内容的完整流程4.1 认识HTTP状态码和响应对象当Requests发送请求后服务器会返回一个状态码它告诉你这次请求的结果。200表示成功301和302是重定向403表示服务器拒绝访问通常是反爬机制生效404是页面不存在429表示请求太频繁被限流了。我之所以专门讲429是因为后面常见问题里会详细展开——这个状态码在爬虫生涯里几乎一定会遇到。response对象有几个常用属性response.text文本形式的响应内容HTML网页就是在这里response.content二进制形式的响应内容适合下载图片、文件response.status_codeHTTP状态码response.json()如果响应是JSON格式直接解析成Python字典response.headers响应头里面可能有Set-Cookie、Content-Type等重要信息response.url最终请求的URL重定向之后会变化response.encoding编码格式有时需要手动指定编码问题值得单独说。很多中文网站用的是UTF-8但有些老站还沿用GBK或GB2312如果response.text出现乱码基本上就是编码不对。解决办法是手动设置response.encoding gbk或者先去response.headers里的Content-Type字段查看charset参数。4.2 GET请求的花式用法参数、headers与Cookie抓取静态网页最常用的就是GET请求。GET请求可以把参数放在URL里也可以放在params字典里。比如抓取搜索页关键词和页码都作为查询参数import requests params { keyword: python爬虫, page: 1 } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } response requests.get(https://example.com/search, paramsparams, headersheaders, timeout10) print(response.url) # 打印实际请求的URL可以看到参数拼在链接后面用params字典而不是手动拼接URL的好处是Requests会自动处理URL编码中文关键词不会被拼坏特殊字符也不用自己转义。我在调试的时候经常打印response.url确认实际发出的请求和预期一致。有些网页需要登录才能访问这时候就需要处理Cookie。最简单的方式是直接在headers里带上Cookie字符串就像浏览器那样。还有一种更规范的方式是用requests.Session()对象——Session对象会在多个请求之间保持Cookie模拟浏览器的会话状态。比如先登录一次后续请求自动携带登录凭证。4.3 POST请求与数据提交虽然入门爬虫大部分场景是GET但POST也很常用——登录、搜索、提交表单都会用到。POST请求的核心参数是data表单数据或jsonJSON数据。import requests login_data { username: my_account, password: my_password } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Content-Type: application/x-www-form-urlencoded } response requests.post(https://example.com/login, datalogin_data, headersheaders, timeout10)注意有些网站的登录接口需要的是JSON格式这时候用jsonlogin_data而不是datalogin_dataRequests会自动把字典转成JSON字符串并设置正确的Content-Type。如果接口返回的数据是JSON用response.json()直接解析。POST请求在爬虫里还有一个潜在风险频繁提交表单会更容易触发网站的反爬机制。所以入门阶段我建议先做GET请求熟悉了请求-响应模型之后再尝试POST。4.4 添加重试机制和错误处理网络请求不可能百分百成功可能是对方服务器暂时出错可能是网络抖动了也可能是请求太频繁被限流。简单的爬虫不加任何错误处理遇到一次失败就直接崩溃这种脚本基本没法用于实际采集。一个比较可靠的做法是结合try-except和循环实现有限次重试import requests import time def fetch_url(url, headersNone, max_retries3): for attempt in range(max_retries): try: response requests.get(url, headersheaders, timeout10) if response.status_code 200: return response elif response.status_code in (403, 404, 429): print(f状态码异常: {response.status_code}) if response.status_code 429: wait_time 2 * (attempt 1) print(f触发限流等待 {wait_time} 秒后重试) time.sleep(wait_time) continue else: return response else: print(f其他状态码: {response.status_code}) return response except requests.RequestException as e: print(f请求异常: {e}) if attempt max_retries - 1: time.sleep(1) return None这个函数的逻辑是对某些状态码比如429做等待后重试对网络异常也做有限次重试超过最大次数后放弃。time.sleep每多一次重试就等待更久这种策略叫退避重试能显著降低被封IP的概率。5. BeautifulSoup解析HTML的实操要点与选择器全解5.1 初始化BeautifulSoup对象与解析器选择拿到HTML文本之后把它交给BeautifulSoup创建解析对象from bs4 import BeautifulSoup soup BeautifulSoup(html_text, html.parser)这里第二个参数是解析器。html.parser是Python标准库的解析器不需要额外安装但容错性和速度都中规中矩lxml解析速度快很多但对某些畸形HTML的处理方式可能和html.parser不同。如果页面结构特别复杂推荐lxml安装方式是pip install lxml。一个有意思的现象是同一个HTML文本用不同解析器得到的结果可能有细微差别。这是因为不同解析器对不严谨HTML的修复策略不同。如果遇到页面明明有某个元素但soup却取不到的情况不妨换成lxml试试。5.2 用find和find_all精确提取目标元素find_all是BeautifulSoup最核心的方法。它的常见用法包括# 按标签名查找所有div div_list soup.find_all(div) # 按class查找 items soup.find_all(div, class_news-item) # 按id查找id在页面中唯一用find更合适 header soup.find(idmain-header) # 按属性查找 links soup.find_all(a, hrefTrue) # 只找带href的a标签 # 查找文本内容包含指定关键词的标签 tag soup.find(div, string关键词)使用class_而不是class是Python语法规定——class是关键字后面加个下划线避免冲突。这是我第一次用BeautifulSoup时卡住的地方看到别人代码里写class_还以为是笔误后来才知道是故意的。find_all返回的是列表可以用索引访问、可以遍历。find则只返回第一个匹配项如果找不到返回None。初学者最容易犯的错误是对find的结果再做find_all时不判空——如果页面结构变化导致找不到元素程序就直接报AttributeError了。稳妥的写法是先判断结果是否为None再操作。5.3 CSS选择器select更简洁的定位方式如果你熟悉前端开发一定会喜欢select方法。它支持CSS选择器语法写起来特别简洁# 选择class为news-item下的所有a标签 links soup.select(div.news-item a) # 选择id为content下的第二个p标签 p2 soup.select(#content p:nth-of-type(2)) # 选择属性data-id等于100的div target soup.select(div[data-id100]) # 选择class既包含active又包含nav的li lis soup.select(li.nav.active)select返回的也是列表但它不支持class_这种参数需要在选择器字符串里写圆点或中括号。我的个人习惯是找单个简单的元素用find找一类相似元素用select因为CSS选择器用一个字符串就能表达复杂的层级关系。5.4 提取标签属性和文本内容的细节拿到标签对象之后下一步是提取数据。常用操作有# 获取标签文本 text tag.get_text(stripTrue) # stripTrue自动去掉首尾空白 # 获取标签属性 href tag.get(href) img_src tag.find(img).get(src) # 直接访问属性字典用法 title tag[title] # 如果属性不存在会报KeyError用get更安全.text和.get_text()一样都是获取所有子文本但get_text(stripTrue)会清理空白字符。处理列表数据时用stripTrue可以避免文本里带一堆\n和空格清洗起来省很多事。属性提取还有一个常见场景有些网页的数据不是直接写在文本里而是存在HTML属性中——比如图片的真实地址、跳转链接的加密参数、data-xxx自定义属性。这时候get(属性名)是唯一的提取方式。先打印出soup仔细观察目标数据在哪个标签的哪个属性里是每次写解析逻辑前必须做的功课。5.5 处理常见HTML结构列表、表格、翻页实际项目的页面结构往往不是单个标签嵌套那么简单。列表页通常是多个结构相同的卡片或列表项表格则按行和列组织。抓取列表项的通用思路是先找到列表项的共同父容器再遍历每个子项提取数据。for item in soup.select(div.news-item): title_tag item.find(h2) link_tag item.find(a) if title_tag and link_tag: title title_tag.get_text(stripTrue) url link_tag.get(href) print(title, url)抓取表格就更有规律了先定位table标签再遍历tr行再取每个行里的td单元格table soup.find(table) for row in table.find_all(tr): cells row.find_all(td) if cells: print([cell.get_text(stripTrue) for cell in cells])翻页的逻辑也很简单先看第二页的URL是怎么变化的通常有两种情况一种是URL里带页码参数?page2一种是点击按钮触发的POST请求。前者直接用循环拼接URL即可后者需要模拟POST参数。6. 完整实战抓取新闻列表页的标题和链接6.1 目标分析与URL结构确认光讲理论不够我用一个完整的实战案例把整个流程串起来。假设我要抓取一个新闻列表页页面结构大概是一个div包含多条新闻条目每条新闻有一个h2标题和一个a标签链接。第一步先手动打开这个页面右键查看源代码确认数据确实在HTML里。如果看到的数据是页面加载后通过JavaScript动态插入的Requests拿到的HTML里可能根本没有这些内容这种情况就要考虑其他方案了。为了确保示例可以复现这里我构造一个结构类似真实新闻站的HTML片段。实际开发中你需要把目标URL换成自己要抓取的网站。6.2 编写第一个完整爬虫脚本我的脚本逻辑分四步发送请求、解析HTML、提取数据、保存结果。import requests from bs4 import BeautifulSoup url https://example.com/news headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } try: response requests.get(url, headersheaders, timeout10) response.encoding utf-8 response.raise_for_status() # 状态码不是2xx时抛出异常 except requests.RequestException as e: print(f请求失败: {e}) exit() soup BeautifulSoup(response.text, html.parser) news_list [] for item in soup.select(div.news-item): title_tag item.find(h2) link_tag item.find(a) if title_tag is None or link_tag is None: continue title title_tag.get_text(stripTrue) link link_tag.get(href) if not link.startswith(http): link url link # 相对路径拼接成绝对地址 news_list.append({title: title, url: link}) for idx, news in enumerate(news_list, 1): print(f{idx}. {news[title]} - {news[url]})这里有两个细节值得展开。response.raise_for_status()会在状态码不是2xx时抛出异常这比手动检查状态码更简洁——因为2xx包括200、201、206等手动检查容易漏掉合法状态码。URL拼接的startswith(http)判断是为了处理页面中使用相对路径的情况——很多网站的链接不是完整的https://开头而是/news/123.html这种相对路径需要手动拼接域名。6.3 数据清洗与保存CSV和JSON的落地写法拿到数据结构之后通常要保存到本地。CSV和JSON是最常用的两种格式Excel可以直接打开CSVJSON适合程序化处理。import csv import json # 保存为CSV with open(news.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, url]) writer.writeheader() writer.writerows(news_list) # 保存为JSON with open(news.json, w, encodingutf-8) as f: json.dump(news_list, f, ensure_asciiFalse, indent2)保存CSV时用encodingutf-8-sig而不是utf-8是为了解决Excel打开中文CSV乱码的问题。utf-8-sig会在文件开头写入BOM标记Excel用这个标记识别UTF-8编码。这是我被乱码折磨过之后学会的。JSON保存时ensure_asciiFalse保证中文正常显示indent2让文件可读性更好。如果数据量大可以考虑分批次存入数据库但入门阶段文件保存足够。6.4 相对链接拼接与编码问题的实战处理刚才代码里提到了相对链接的拼接这里详细说清楚。页面里的链接可能有几种形式https://example.com/news/123.html绝对路径不需要处理//example.com/news/123.html协议相对路径需要拼接https:/news/123.html站内相对路径需要拼接域名news/123.html相对于当前路径的相对路径拼接规则更复杂我的做法是优先处理前三种最常见情况用urllib.parse.urljoin可以统一处理from urllib.parse import urljoin base_url https://example.com/news full_url urljoin(base_url, link)urljoin是专门为URL拼接设计的函数不管link是绝对路径还是各种相对路径它都能正确拼接。这是我后面写爬虫几乎必用的工具。编码问题的实战场景是如果页面meta标签声明了charsetgbk但Requests把页面当成了utf-8来处理就会出现乱码。这时候先用response.encoding response.apparent_encoding试试——Requests会通过内容分析猜测编码。不过这样有时也会猜错手动指定编码反而更可靠。7. 进阶技巧Session会话、异步请求与限流应对7.1 用Session保持登录状态的正确姿势入门阶段的爬虫大多数不需要登录但很多真实项目需要。用Session对象比每次请求都带Cookie要方便得多。Session对象会自动保存服务器返回的Cookie在后续请求中自动携带。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) # 先登录Session会自动存储cookie login_data {username: my_account, password: my_password} session.post(https://example.com/login, datalogin_data, timeout10) # 之后再请求需要登录的页面 response session.get(https://example.com/profile, timeout10) print(response.text)Session的另一个好处是连接复用。每个请求都需要建立TCP连接和断开连接Session对象会复用连接减少握手开销连续请求速度快很多。7.2 requests的代理设置与HTTPS证书处理有些网络环境需要配置代理才能访问目标网站。Requests的代理设置很简单proxies { http: http://127.0.0.1:7890, https: http://127.0.0.1:7890 } response requests.get(url, proxiesproxies, timeout10)如果遇到HTTPS证书验证问题——比如目标网站证书无效、自签名证书、或者公司网络做了SSL拦截Requests会抛SSLError。临时解决办法是verifyFalseresponse requests.get(url, verifyFalse, timeout10)但这会关闭证书验证有中间人攻击风险。正常做法是在requests库的依赖certifi更新证书或者设置REQUESTS_CA_BUNDLE环境变量指向正确的CA证书。7.3 请求频率控制sleep退避和429状态码应对爬虫抓数据时最常遇到的问题就是请求太频繁被限流。服务器通常通过IP单位时间内的请求次数来判断——超过了阈值就返回429状态码或者直接封IP。我看到很多人在热词搜429 too many requests、exceeded retry limit说明大家都遇到过这个。应对策略很简单核心就一个字慢。在两个请求之间加time.sleep()模拟人的操作速度。比如import time import random for page in range(1, 10): url fhttps://example.com/news?page{page} response requests.get(url, headersheaders, timeout10) if response.status_code 429: wait_time random.uniform(5, 10) print(f触发限流等待 {wait_time:.1f} 秒) time.sleep(wait_time) # 处理response time.sleep(random.uniform(1, 3))加随机延迟而不是固定延迟是为了让请求节奏更接近人类行为。固定间隔反而容易被识别为机器行为。random.uniform(1, 3)表示每次等待1到3秒之间的随机时间。对429状态码的正确处理是检测到429时先停一段时间再重试。很多网站会在响应头里告诉你需要等多久比如Retry-After字段可以读取这个值决定等待时间。7.4 分布式爬虫的入门理解何时需要升级方案热词里有分布式爬虫这个词说明大家对扩展方案有兴趣。分布式爬虫的核心思想是将抓取任务拆分到多个节点上并行执行每个节点负责一部分URL共享一个任务队列最后把结果汇总。但我要泼一盆冷水对于个人学习或小型项目分布式完全是过度设计。Requests加单线程Python已经能拿下绝大多数中小规模数据采集需求。只有当单机性能确实不够——比如要抓取千万级页面、对方分散了多台服务器——才需要考虑分布式方案。而且分布式不等于更快它解决的是单机被限流、单机带宽不够的问题。入门阶段先把Requests和BeautifulSoup用熟练远胜过急着搭一套用不上的分布式集群。8. 常见报错与排查技巧实录从403到429的完整解法8.1 403 Forbidden被反爬机制拦下的排查顺序403表示服务器拒绝了请求这是入门爬虫最常遇到的状态码。看到403我的排查顺序是第一步查User-Agent。Requests默认的UA是python-requests/x.x.x服务器一看就知道是爬虫。解决办法是伪装成浏览器换一个完整的UA字符串。这一步能解决一半以上的403。第二步查headers是否完整。有些网站会检查Accept、Accept-Language、Referer等头部信息。尤其是Referer直接访问和从站内跳转过来的Referer不一样某些网站会校验这个字段。把浏览器的完整header复制过来是最快的方法。第三步查Cookie。有些网站即使不登录也会先下发一个Cookie然后用这个Cookie验证后续请求。如果你只带了UA没带Cookie服务器一样拒绝。用Session对象先访问首页拿到Cookie再访问目标页面往往就能解决。第四步查访问频率。如果之前的请求太猛IP可能已经被临时封禁。等一段时间再试或者换代理IP。8.2 429 Too Many Requests限流后的正确应对方式429状态码我专门拿出来说因为它和403原因不一样——403通常是被识别为爬虫429是你的请求频率超过了服务器阈值。这个状态码多到连OpenAI的API都会返回热词里能看到exceeded retry limit, last status: 429 too many requests这种报错说明连AI领域的开发者也在被同样的问题折磨。解决429的思路从宏观到微观分为三层第一层是降低请求频率增加sleep时间。这是最简单直接的手段把请求间隔从1秒调整到3到5秒绝大多数429问题都能缓解。第二层是加退避重试机制。像前面写的fetch_url函数那样检测到429后等待一段时间再重试每次重试的等待时间递增。这样即使偶尔撞上限流也能自动恢复。第三层是分布式IP池。每个IP的请求配额是固定的如果有多个IP轮换使用总吞吐量就能成倍增长。但这需要额外的代理资源入门阶段不需要考虑。8.3 编码乱码、空列表和解析不到的排查方法解析不到数据的时候程序一般不报错只是结果为空。这是爬虫开发里最让人抓狂的问题——没有异常就是拿不到东西。我的排查步骤是第一步把拿到的HTML保存到本地文件用编辑器打开看。很多时候抓取到的不是预期页面而是一个登录跳转页、一个验证码页、或者一个请求频率过快的提示页。不看原始HTML永远不知道发生了什么。第二步检查解析选择器。用CSS选择器时一点小错误就匹配不到元素。在浏览器开发者工具里先用document.querySelectorAll验证选择器的有效性再搬到BeautifulSoup里可以少走很多弯路。第三步确认元素是否在HTML源码中。如果目标数据是JavaScript动态生成的response.text里根本找不到。用浏览器查看源代码CtrlU而不是开发者工具的元素面板——后者显示的是渲染后的DOM不代表服务器返回的HTML。8.4 请求超时与连接中断网络层面的问题定位stream disconnected before completion: too many pending requests这类报错说明请求还没完成连接就断了通常是并发请求太多、服务器主动断开或者本地网络不稳。Requests默认是阻塞式请求一个请求完成才发下一个一般不会出现并发问题。但如果用了多线程就需要控制并发数避免同时发出太多请求。超时问题的排查也比较直接。如果timeout10总是触发超时可能是目标网站太慢也可能是代理不可用。先试试不经过代理直连再试试换一个网络环境判断是本地的网络问题还是目标服务器的问题。9. 爬虫的边界意识什么能爬什么不能爬讲了这么多技术细节最后必须聊聊边界。爬虫是一把刀能切菜也能伤人关键看怎么用。如果你是做个人学习、数据分析、学术研究抓取公开的非敏感信息一般都没问题。但要注意几点第一遵守robots.txt约定。这个文件位于网站根目录声明了哪些路径允许爬虫访问。虽然它没有法律强制力但钻进一个明确禁止爬虫的目录性质就变了。第二控制请求频率不要对目标网站造成压力。一个每秒请求数百次的爬虫即使内容完全合规本质上也是对服务器资源的滥用。我见过太多人为了抓一个很小的数据集把人家网站的服务器打崩了。第三注意数据的使用边界。抓取的信息可能涉及个人隐私、版权内容或商业机密。抓下来自行分析是一回事公开传播就是另一回事。尤其是涉及个人手机号、身份证号、住址等敏感数据无论如何都不应该被抓取和存储。第四不要破解登录验证机制。如果目标网站用验证码、加密参数等手段明确阻止程序访问强行绕过已经属于攻击行为。做一个合格的数据使用者抓取公开内容时保持克制遇到防护边界就止步这才是一个成熟的爬虫开发者的做法。技术本身不分善恶但每一个写爬虫的人都有选择的权利。真正的爬虫高手不是突破了多少防线而是知道什么时候应该停下来。我个人在实际操作中的体会是Requests加BeautifulSoup这套组合熟练之后写一个简单爬虫也就一二十行代码但千万别觉得简单就轻视它。很多高级的爬虫方案核心逻辑依然离不开HTTP请求和HTML解析这两件事。把基础打牢后面学什么框架都快。最后再分享一个小技巧写爬虫时别急着把代码一次写完先在小范围验证请求成功、解析正确再扩大规模。这样踩坑的概率会小很多排查问题也快得多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门