ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python爬虫入门:urllib与requests请求库解析与实战

Python爬虫入门:urllib与requests请求库解析与实战 想学Python爬虫的人十个里有八个是从“我想自动下载点数据”开始的。有人想抓电商价格有人想存豆瓣电影排行榜还有人单纯觉得手动复制粘贴太蠢。不管动机是什么第一关永远是同一个问题HTTP请求怎么发。这个系列我打算从最基本的 urllib 和 requests 讲起它们就是绝大多数Python爬虫的地基搞懂这两个库你后面看任何框架、任何分布式方案都不会慌。这篇文章不假设你有任何爬虫基础但默认你至少会跑一个Python脚本。环境没搭好的豆油我把安装和配置也一并写清楚。目前市面上的教程要么一上来就教Scrapy学得云里雾里要么只抄个requests.get完事遇到反爬就傻眼。我尽量把两个库的原理、区别、坑位都讲透让你学完之后不仅能跑通代码还知道代码在背后干了什么。1. 动手前的环境准备Python、编辑器与依赖安装1.1 为什么用Python写爬虫在网络爬虫这件事上Python几乎成了默认语言原因其实很朴素。第一语法够简单同样是发一个GET请求Java和C#写起来要一堆样板代码Python一两行就结束第二生态太全了从请求、解析、存储到调度每个环节都有现成库能用第三社区资料多你遇到“请求被拒绝”“编码乱码”这种问题时搜一下基本都能找到答案。当然这不代表其他语言不能写爬虫Node.js、Go、Java都有各自的优势只是对新手来说Python的上手成本最低。爬虫的核心逻辑就那么几步拿到URL带上请求头发请求拿响应解析数据存数据。用Python去串这套流程最顺手。1.2 安装Python与配置IDE写代码之前得先把环境弄好。这里我建议直接用Python 3.8以上的版本别再用Python 2了官方早就停止维护第三方库也纷纷抛弃了它。Windows用户去官网下载安装包的时候有一个特别容易被忽略的点安装页底部有一个“Add Python to PATH”的复选框一定要勾上不勾的话后面在命令行里敲python会提示找不到命令光这个问题就能劝退一批新人。macOS和Linux用户就简单得多系统一般自带Python 3实在想要新版用包管理器装就行。装好之后按WinR输入cmd或者直接在终端里敲一行python --version能输出版本号就说明环境OK了。编辑器这块我推荐两条路。如果你的电脑配置一般或者不想装太重的工具用VSCode装一个Python插件就够了自动补全、调试、终端都有了。如果更喜欢“开箱即用”的体验可以选PyCharm社区版专门为Python设计但初次启动会慢一些配置文件也多一些对小白反而有点负担。我个人日常用的是VSCode轻量跑爬虫脚本完全够用。1.3 安装requests库urllib是Python自带的不需要安装但requests不是它是第三方库需要单独装。打开命令行输入pip install requests如果你想指定版本或者在某些特殊环境里装不上可以换国内镜像源加速pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后在Python里试着导入一下import requests print(requests.__version__)能正常输出版本号就说明库没问题。如果提示ModuleNotFoundError大概率是pip和python的版本对不上比如你电脑里装了多个Python版本pip装到了旧版上。这种情况最简单的办法是用python -m pip install requests来装确保装进当前这个解释器里。2. urllib不依赖第三方库的最基础请求方式2.1 urlopen的用法与响应的读取urllib是Python标准库不用安装也不用配置环境打开编辑器直接就能写。先看一个最简单的请求from urllib.request import urlopen resp urlopen(https://example.com) print(resp.status) # 200 html resp.read() # 读取字节内容 print(html.decode(utf-8)) # 解码成字符串这里三个关键点要讲清楚。第一urlopen返回的是一个类文件对象你得用read()去读内容读出来的是字节类型bytes不是字符串。第二字节内容要通过decode()转成字符串转码用哪种字符集取决于目标网页用的是UTF-8还是GBK这个在后面讲编码问题时会重点说。第三resp.status是HTTP状态码200代表成功404代表页面不存在状态码在爬虫里是判断请求是否成功的直接依据。很多人刚接触时会觉得urlopen挺好用啊为什么网上都说它难用因为这只是最简单的场景。等你需要添加请求头、处理Cookie、保持会话时就会体会到urllib的写法相当啰嗦。2.2 搞定请求头、超时与POST表单很多网站看到请求头里有Python-urllib/3.x这种标记会直接拒绝服务。所以实际爬虫里我们几乎不会用裸的urlopen而是构造一个Request对象把headers传进去from urllib.request import Request, urlopen headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 } req Request(https://httpbin.org/get, headersheaders) resp urlopen(req, timeout5) print(resp.read().decode(utf-8))看到那个User-Agent了吗这一步是新手最容易忽略的。它告诉服务器“我是谁”浏览器请求会带上一长串UA标识而代码请求默认会暴露自己的身份。很多网站的反爬策略第一步就是识别这个字段一旦发现你不是浏览器就拒绝返回数据或者返回一个验证页。所以无论用urllib还是requests把User-Agent伪装成Chrome或Firefox是爬虫的基本素养。timeout5也很重要意思是连接超过5秒就放弃。不设置这个参数遇到一个死活连不上的域名你的程序可能卡几分钟才报错太影响效率了。POST请求在urllib里要多一步把要提交的数据编码成字节from urllib.request import Request, urlopen from urllib.parse import urlencode data urlencode({username: test, password: 123456}).encode(utf-8) req Request(https://httpbin.org/post, datadata, headers{User-Agent: headers.get(User-Agent)}) resp urlopen(req)urlencode的作用是把字典转成usernametestpassword123456这样的查询字符串然后再转成字节传入。这一套流程也不是不能做就是每一步都得自己手动来代码就慢慢臃肿起来了。2.3 urllib的短板在哪用了一段时间urllib后你会发现几个痛点。首先是编码处理requests会自动猜网页编码urllib不会你得手动decode其次是Cookie和会话管理urllib没有内置Session的概念想要保持登录态你得自己写一个HTTPCookieProcessor来处理Cookie代码量大且容易出错再就是重定向、重试、压缩这些高级功能全都得手动开。这些短板不是不能用urllib而是用起来不够“顺手”。我见过很多老爬虫项目里直接用urllib的但它们的代码普遍比requests版本长一截。所以对于新手我建议把urllib作为理解HTTP请求原理的工具来学真正写爬虫代码时用requests这样既能理解底层又不至于被细节拖累。3. requests更顺手的请求库3.1 一个最简单的GET请求与响应对象requests的口号是“让HTTP服务人类”这句话真不是吹的。同样一个GET请求urllib写了七八行requests两行搞定import requests resp requests.get(https://httpbin.org/get) print(resp.status_code) print(resp.text)resp.status_code是状态码resp.text是解码后的字符串不用再手动调decode。如果接口返回的是JSON格式requests还给了你一个更香的方法——resp.json()直接给你解析好的字典或列表。data resp.json() print(data)还有resp.encoding可以查看或设置编码。比如遇到一个页面乱码了大概率是编码识别错了你手动指定UTF-8或GBK就行resp.encoding utf-8 print(resp.text)3.2 params、headers与POST表单有参数要带怎么办很多人刚开始会直接拼URL字符串比如https://httpbin.org/get?nametonyage18这种硬拼的方式能用但参数一多、含特殊字符时很容易拼错。requests推荐的做法是用params字典params {name: tony, age: 18} resp requests.get(https://httpbin.org/get, paramsparams) print(resp.url) # 实际请求的URL它会自动帮你把字典转成正确的查询字符串并对特殊字符做URL编码。再来看请求头。同样的UA伪装requests写起来更简洁import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 } resp requests.get(https://httpbin.org/get, headersheaders)POST提交两种常见格式表单提交和JSON提交。表单提交用data参数JSON提交用json参数# 表单提交 resp requests.post(https://httpbin.org/post, data{username: tony}) # JSON提交 resp requests.post(https://httpbin.org/post, json{role: admin})两者的区别在于服务端接收到的Content-Type不同一个是application/x-www-form-urlencoded一个是application/json。很多现代接口只认JSON用错格式会报参数错误这一点踩过坑的人都懂。3.3 Session与连接保持爬虫里有一个场景你一定绕不开先登录再带着登录状态去访问其他页面。如果每次请求都是独立的服务器没记住你那登录就白登了。这时候你要用requests.Session()import requests sess requests.Session() sess.headers.update({User-Agent: Mozilla/5.0 ...}) login_resp sess.post(https://httpbin.org/post, data{username: test, password: 123}) print(login_resp.status_code) # 后续所有用sess发起的请求都会自动携带服务器留下的Cookie home_resp sess.get(https://httpbin.org/cookies) print(home_resp.text)Session的作用可以理解成一个“小本本”它会自动记录服务器返回的Cookie并在后续请求中带上。用Session还有个好处可以统一设置headers、统一设置超时不用每次请求都重复写。在实际爬虫里Session几乎是标配特别是需要登录的站点没有Session寸步难行。3.4 超时、重试与合理限速requests的get和post方法都支持timeout参数它控制的是连接和读数据的最长等待时间。我强烈建议每个请求都写上超时因为爬虫是循环请求只要其中一个请求卡住整个程序就会停住一个超时都可能导致任务中断几小时。try: resp requests.get(https://httpbin.org/get, timeout(3, 10)) except requests.exceptions.Timeout: print(请求超时了)这里的(3, 10)意思是连接超时3秒读取超时10秒。没有响应就抛超时异常你可以根据异常做重试。但光有超时还不够爬虫里经常遇到一个麻烦问题请求太频繁服务器返回429。429状态码的中文含义是“请求过多”服务器在明确告诉你你太快了我处理不过来或者我不想处理你。这时候最有效的方法就是减速比如每次请求之间随机停几秒import time import random for i in range(5): resp requests.get(https://httpbin.org/get) print(resp.status_code) time.sleep(random.uniform(1, 3))如果你的爬虫遇到了exceeded retry limit, last status: 429 too many requests这种报错不要慌说明你被限流了需要放慢频率。这里还可以用requests的适配器来配置重试策略稍后我会在常见问题章节详细展开。4. urllib与requests的选型对比4.1 一张表看完核心差异经常有刚入门的朋友问我既然urllib是自带的那直接用urllib不就好了其实两个库的核心差异用一个实际场景就能说清同样带Cookies访问同一页面requests用Session一行代码搞定urllib要开一个HTTPCookieProcessor加一个build_opener至少三五行代码。我用一张表把常见场景的差异整理出来对比维度urllibrequests安装方式标准库自带需要pip安装GET请求需构造Request对象细节多get方法一行搞定URL参数手动urlencode再拼params字典自动编码响应编码手动decode需猜编码text属性自动识别编码状态码判断resp.statusresp.status_codeCookie会话需要CookieProcessorSession自动管理超时异常处理起来略繁琐异常类型清晰重试策略自己写循环适配器可配置上手难度偏底层偏简洁这张表看下来requests在绝大多数场景下都更省心urllib的优势主要在于“零依赖”和“系统内置”。4.2 实际项目的选型建议那是不是完全不用学urllib了也不是。我的建议是理解urllib的原理写代码时用requests。为什么这么说第一urllib能帮你理解HTTP请求的本质。requests把很多细节封装掉了你根本不知道底层发生了什么一旦出错就无从排查。而用urllib走一遍urlopen、Request、字节decode的流程你脑海里就有了HTTP请求的完整链路。第二有些场景你真用不上requests。比如你写一个必须在极简环境运行的脚本或者部署到别人的机器上不方便装第三方库这时候urllib就是救命的。再比如很多爬虫框架的底层用的还是urllib或它衍生出的机制。第三如果只是日常写爬虫或者说你要学的下一站是Scrapy、playwright这类工具那requests是更好的起点因为它足够快、足够简单能让你把精力放在“数据怎么解析”而不是“请求怎么发”上。我的结论很明确先花一两个小时跟着本文把urllib的代码敲一遍搞懂发送请求的底层逻辑然后切换requests用它作为你的主力请求库上手写爬虫。这样对你理解和找工作都更稳妥。5. 第一个小爬虫抓取并解析一个公开JSON接口5.1 先看目标接口返回什么学了这么多来点实战。这里我选了一个公开的测试接口JSONPlaceholder它模拟了常见博客系统的文章数据接口地址是https://jsonplaceholder.typicode.com/posts/1。这个接口不需要登录不会被反爬非常适合练手。你可以先在浏览器地址栏打开它看到的是类似这样的JSON{ userId: 1, id: 1, title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit, body: quia et suscipit... }这就是我们爬虫要“抓”的目标。从这个例子里你能明白一个道理很多网站或应用看起来是网页背后其实是接口在提供数据。我们的爬虫如果能直接请求接口拿到的数据比解析网页HTML要干净得多。5.2 完整代码与逐步讲解下面用requests写一个完整的爬虫代码目标是把标题和正文抓下来并打印import requests def fetch_post(post_id): url fhttps://jsonplaceholder.typicode.com/posts/{post_id} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 } try: resp requests.get(url, headersheaders, timeout5) resp.raise_for_status() data resp.json() print(f文章ID: {data[id]}) print(f标题: {data[title]}) print(f正文: {data[body][:50]}...) except requests.exceptions.RequestException as e: print(f请求失败: {e}) if __name__ __main__: fetch_post(1)这段代码里有一个特别值得说的点raise_for_status()。它的作用是当状态码不是200时主动抛出一个异常而不会让程序带着错误的状态码继续执行。比如接口返回404你还会继续去解析数据吗如果没这个判断resp.json()就会因为内容不是JSON而报错错误提示也不直观。加了这行之后状态码一有问题程序就会明确告诉你“请求出错”。resp.json()是requests解析JSON的便捷方法。如果接口返回的Content-Type是application/jsonrequests会自动识别并解析成Python的字典即便Content-Type不对只要内容是JSON它也能正常解析。你还可以顺手把这个流程扩展成“循环抓取多篇文章”只需要再加一层for循环每抓一次停1秒避免请求过快。这样做之后一个真正意义上的小爬虫就诞生了。5.3 运行结果与可能遇到的问题运行上面的代码正常会输出文章ID: 1 标题: sunt aut facere repellat provident occaecati excepturi optio reprehenderit 正文: quia et suscipit...这就是你的第一个爬虫虽然简单但它已经覆盖了爬虫的核心链路构造URL、带UA、发请求、判断状态码、解析数据。后面所有爬虫无非是在这个链路上增加更复杂的数据解析、存储和调度策略。如果你运行时报了SSL证书相关的错误大概率是公司网络或者系统证书有问题。最简单的临时处理办法是加一个verifyFalse参数同时屏蔽掉警告import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) resp requests.get(url, headersheaders, verifyFalse)这个操作确实不严谨但从学习角度它能让你先跑通流程。等正式项目里再用正确的证书验证方式替换。6. 新手最容易踩的坑429、编码与合规6.1 429状态码被限流了怎么办最近搜索爬虫相关问题时很多人会遇到exceeded retry limit, last status: 429 too many requests这样的报错。429表示你的请求频率太高服务器的防火墙或反爬系统已经启动限流策略。我见过不少新手上来就写一个无限循环每秒钟发几十个请求结果不仅数据没抓到IP还被封了。处理429的思路有三步。第一步降低请求频率在每两次请求之间加一个随机延时比如time.sleep(random.uniform(1, 5))第二步设置重试策略可以使用requests的适配器from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session requests.Session() retry Retry( total3, backoff_factor1, status_forcelist[429, 500, 502, 503], ) adapter HTTPAdapter(max_retriesretry) session.mount(http://, adapter) session.mount(https://, adapter) resp session.get(https://httpbin.org/get, timeout5)backoff_factor1是一个非常讲究的参数它控制重试的等待时间。第一次重试前等1秒第二次等2秒第三次等4秒按指数增长。为什么要递增因为服务器限流时你越快重试越容易被继续封等一等反而更有可能恢复访问。第三步如果你的请求量实在太大可以轮换不同的User-Agent模拟多个浏览器访问降低被识别为同一爬虫的风险。总体来说遇到429不要硬刚服务器有服务器的底线爬虫要懂得“礼貌”。6.2 乱码与编码问题爬虫里有一类问题特别让人抓狂数据抓下来了但全是乱码。其实乱码的原因只有一个——你用来解码的字符集和网页实际使用的字符集不一致。中文网站常见的编码是UTF-8和GBK两种。用requests时它通常会根据响应头里的charset自动判断编码但有些网站响应头没写明白或者写得不对就会导致乱码。这个问题的排查方法也很简单先打印一下resp.encoding和resp.apparent_encoding前者是requests认为的编码后者是requests通过内容分析出的编码。当两者不一致时以apparent_encoding为准手动设置resp.encoding resp.apparent_encoding print(resp.text)这个一行代码的小技巧几乎能解决90%的乱码问题。剩下10%的一般是页面里用JS动态渲染出来的内容这种靠requests拿不到得用playwright这类支持执行JS的工具来做那是后面系列文章的话题了。6.3 SSL证书、超时与重定向SSL证书错误也是一个高频问题表现是报错信息里带SSL: CERTIFICATE_VERIFY_FAILED。出现这个情况在爬虫开发调试阶段可以直接用verifyFalse跳过证书验证但前提是你能承担一定的安全风险。如果目标站点是正规的HTTPS站点跳过验证只是省去证书校验实际操作中问题不大。还有一个容易被忽略的情况是重定向。requests默认会自动跟随重定向比如你访问http://example.com它会自动跳到https://example.com。这通常没问题但有一种例外有的接口用302重定向做登录验证或防盗链你不希望它跳只想看到这个302响应。这时候可以设置resp requests.get(url, allow_redirectsFalse)想看你到底被跳转到了哪里可以检查resp.headers.get(Location)。理解重定向逻辑对排查“为什么打印的状态码是200但内容不对”这种问题很有帮助。6.4 爬虫的边界与合规提醒最后这部分才是真正想提醒你的地方。我见过太多初学者把爬虫当成了“无敌的勺子”想抓什么抓什么完全不考虑对方网站的承受能力。爬虫虽然是技术活但它也有基本的行为边界。学习阶段请务必控制请求频率尊重目标网站的robots.txt文件。所谓robots.txt就是网站告诉你哪些路径允许爬、哪些不允许爬的说明你可以在浏览器地址栏输入https://example.com/robots.txt看一看。另外绝对不要用爬虫去抓取需要登录才能访问的私密数据、个人隐私数据也不要去抓取涉及商业机密的非公开接口。学爬虫是为了自动化处理公开数据而不是为了突破访问控制。这里我也掏心窝地说一句技术本身没有善恶但用技术的人得有点敬畏心。早期爬虫圈流传着不少因为爬取不当而惹上麻烦的故事大多数情况下只要你低调、低速、遵守规则学爬虫就是一项非常有趣又能提升效率的技能。如果你是想做商业爬虫项目提醒一句别只盯着技术合规和法律风险一定要提前咨询清楚别让自己陷入被动。这行最忌讳的就是心存侥幸。按照这个系列的计划下一篇我会接着讲数据解析重点是如何从HTML页面里把结构化数据提取出来到时你会用到BeautifulSoup和lxml一步步把“网页版”的数据也变成干净可用的表格。学会了请求和解析你就真正拥有了独立写爬虫的能力。
返回列表