ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python爬虫实战:从零抓取豆瓣电影Top250并存储CSV

Python爬虫实战:从零抓取豆瓣电影Top250并存储CSV 做爬虫项目有一种天然的成就感明明只是一堆标签和链接你却能把它变成一张结构整齐的表格。很多初学者选择练手项目时都会盯上豆瓣电影排行这个页面结构稳定、数据字段干净更重要的是它足够有名爬出来的数据自己能看懂、能玩起来不会像某些杂乱页面一样爬完都不知道对不对。如果你已经学完了 Python 基础语法但还没做过一个像样的实战项目这篇文章就是给你准备的。我默认你会 Python 的基本语法、能用 pip 装包但没深入学过 requests、正则表达式或者 BeautifulSoup都没关系。我会带你从环境搭建开始一直写到把排行榜数据存储成文件中间包括完整的源码、为什么这么写的逻辑以及一堆我实际踩过的坑。1. 项目概述与整体思路1.1 为什么爬虫入门首选豆瓣电影排行几乎所有爬虫教程都会拿豆瓣电影 Top250 当案例这真不是巧合。我自己也带过一些新人发现豆瓣这个页面对学习者来说有三点天然优势是其他网站比不了的。第一结构简单且规律。Top250 是服务端直接渲染的静态页面你请求一个 URL拿回来的 HTML 里就直接包含完整的电影信息不需要对接 JSON 接口也不需要模拟点击。每页正好 25 条一共 10 页翻页规则就是 URL 里start参数每次加 25。这种一板一眼的分页规律特别适合讲解循环抓取多个页面的学习场景。第二数据密度高且规整。每一部电影的信息包含排名、电影名、导演和主演、上映年份、国家、类型、评分、评价人数、经典台词横跨了文本提取、正则匹配、列表清洗等多个解析技巧。字段多意味着你能练到的东西也多但每个字段的位置稳定又不会难到劝退。第三识别结果直观可验证。爬完之后你马上就能核对第一名是《肖申克的救赎》9.7 分。如果你解析出来的数据和页面上看到的不一样肯定是某个环节出了问题排查起来非常方便。对比爬一些结构混乱的小网站你连正确答案都没有出了问题都不知道该信谁。1.2 爬虫项目的四步通用流程不管爬什么网站思路都是同一套我把每一步对应到生活场景里帮你理解。第一步是获取网页相当于你去菜市场把菜买回来。Python 里这一步通常用 requests 库发出 HTTP 请求服务器把 HTML 文档返回给你。第二步是解析网页相当于把买回来的菜摘干净、洗干净。HTML 就是一坨混杂了标签、属性、文本的字符串你需要用解析库把想要的信息从里面挑出来。第三步是提取数据相当于把菜切成你需要的形状。上一步的解析只是把结构理顺这一步则是精确定位到每一个电影名、每一个评分把它们变成 Python 里的字符串和数字。第四步是存储数据相当于把做好的菜装盘上桌。你可以把结果打印到屏幕上也可以写入 CSV 文件、Excel 表格或者数据库。这四个步骤是递进关系任何一个环节出问题后面全部白搭。所以我建议你跟着本文的节奏一步一步来每完成一步可以先运行一下看看输出不要攒到最后一次性调试那样出错了你会很难定位问题到底在哪。2. 环境搭建与工具选型2.1 Python 环境和基础依赖动手之前先确认电脑上的 Python 环境。这里我不推荐用某些网站自带的在线编程环境因为爬虫涉及网络请求在线环境经常对出站连接做了限制代码本身没问题却怎么也跑不通非常打击信心。建议在本地安装 PythonWindows 用户注意安装时勾选Add Python to PATH这个选项否则命令行里敲python会提示找不到命令。安装完成后打开终端Windows 是 cmd 或 PowerShellmacOS 是 Terminal敲下面这条命令确认版本python --version只要版本号是 3.6 及以上就完全够用了。如果你装的是 3.10 或者更新的版本反而要注意某些第三方库的兼容性问题不过本文用到的库都比较成熟问题不大。还需要一个趁手的代码编辑器。我没法替你决定用哪个但这个项目规模不大用 VS Code 就足够了装一个 Python 扩展插件就能获得代码高亮、智能提示和单文件直接运行的能力。新手不推荐一上来就折腾各种重型 IDE等你觉得编辑器本身就是瓶颈的时候再说。2.2 三个核心库requests、BeautifulSoup4 与 lxml整个项目我只依赖三个第三方库你可以在终端执行下面的命令一次性安装pip install requests beautifulsoup4 lxmlrequests 库负责发出 HTTP 请求。Python 标准库其实自带urllib也能发起请求但用起来非常别扭设置请求头、处理编码都要写一大堆样板代码。requests 把底层的 HTTP 协议细节打包成了直观的 API一个get()方法就能拿到响应内容业界评价它是一个让 HTTP 服务变得人类可用的库这个形容非常精准。BeautifulSoup4常简写为 bs4负责解析 HTML。你可以类比一下去菜市场买菜requests 是把整捆菜拿回来BeautifulSoup 是帮你把菜根、烂叶子、黄叶分拣出来让你能直接挑走想要的菜心。它的底层依赖一个更快的解析器 lxml所以我们要同时安装。这里要解释一下为什么不推荐用正则表达式直接匹配电影名。正则确实有能力从 HTML 字符串里抠出任何信息但 HTML 是一门层级嵌套的语言同一段信息可能因为属性顺序不同而产生多种写法正则会越写越复杂、越写越脆弱。BeautifulSoup 把 HTML 转换成一棵可遍历的树按结构取数据写起来直观后期改起来也简单。你可以这么理解正则像是在一堆积木里凭手感找特定的一块BeautifulSoup 则是先把积木按图纸搭好再告诉你哪块在哪一层。下面这个表格简单对比两套方案的适用场景方便你心里有数方案优点缺点典型场景正则表达式灵活、不依赖解析库嵌套结构难处理、可读性差从普通文本里抽取数字或邮箱等简单模式BeautifulSoup处理复杂 HTML 结构高效、易读需要额外安装依赖、速度略慢于正则需要按标签层级定位信息的场景2.3 设置请求头为什么必须伪装成浏览器第一次写爬虫的人最容易犯的错就是直接运行requests.get(url)然后什么都拿不到只看到一个 418 或者 403 的状态码。为什么因为服务器可以通过请求头里的User-Agent字段判断访问者的身份。浏览器发出请求时这个字段会带上完整的浏览器版本和操作系统信息而代码用 requests 默认值发出的请求User-Agent是python-requests/x.x.x服务器一眼就能认出这不是真人访问直接拒绝响应。这就像你穿着厨师服去会议室开会保安当然会拦住你。所以第一步最常规的操作是准备一组看起来像浏览器的请求头headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 }这组User-Agent字符串不是必须一字不差你可以用自己浏览器里的版本号但格式和核心字段最好保持完整。设置好请求头之后再发请求服务器就会把你当成一个普通访客。后续我还会聊到如果追求更严谨的表现可以用某些库随机切换User-Agent但对这个项目来说一个固定的浏览器版本号就够了。3. 页面分析与数据定位方法3.1 URL 分页规律与请求参数豆瓣电影 Top250 的入口地址就是https://movie.douban.com/top250。打开页面拉到最底部你会看到上一页和下一页的链接观察这些链接的 URL可以总结出分页规律。首页地址是https://movie.douban.com/top250?start0filter第二页是https://movie.douban.com/top250?start25filter第三页是start50以此类推。规律非常明显start参数表示从第几条数据开始返回每页 25 条下一页就把start增加 25。filter参数对结果没有实质影响保留即可。用数学方式表达就是第 n 页从 0 开始计数的 URL 是https://movie.douban.com/top250?start{n*25}filter。总共有 250 条数据也就是start从 0 循环到 225共 10 页。这个规律是后面翻页循环的基础你可以先手动把第二页、第三页的 URL 粘到浏览器打开亲眼确认一下数据确实不同这样写代码时心里更踏实。3.2 用浏览器开发者工具定位元素位置拿到 HTML 之后怎么知道电影名藏在哪个标签里这个环节不需要盲猜浏览器的开发者工具就是你的地图。我用的方法很简单在豆瓣 Top250 页面上点击右键选择检查或者按 F12然后在开发者工具的 Elements 面板里把鼠标移动到某个电影标题上。此时浏览器会高亮对应的 HTML 代码那就是你需要在代码里解析的位置。我带你逐层拆解一下单部电影的信息结构方便你理解后面的选择器写法。每部电影都被包裹在一个li标签里这个li的class属性是item。在这个li内部左侧是图片和排名右侧是电影信息。排名写在div classpic下面的第一个em标签里电影标题分中文名和英文名中文名在div classhd下面的span classtitle里评分在span classrating_num里评价人数则在div classstar里的最后一个span。多次实战下来我建议你在浏览器里至少做三次这种定位操作定位一个标题、一个评分、一条经典台词。做多了你会发现HTML 结构远看是一堆乱七八糟的尖括号但一旦理解了一套 CSS 命名规则和层级习惯看起来就和看报纸的版面一样清晰。3.3 待提取字段清单动手写代码之前先把目标字段固定下来。我自己做爬虫的习惯是先列一个字段清单再对照清单检查代码防止边写边漏。这个项目我们要提取的字段是这六个字段名含义示例值rank排名1title中文电影名肖申克的救赎rating豆瓣评分9.7quote经典台词希望让人自由。year上映年份1994region_genre国家/类型美国 / 犯罪 剧情这里有个容易忽略的细节quote经典台词不是每部电影都有没有的情况下页面会显示为空提取时要做空值处理否则存入文件时会留下 None 或者报错。year在上映年份在页面上是这么写的1994 / 美国 / 犯罪 剧情我们要的年份是这段字符串里的第一个数字后面写代码时我用正则来提取。4. 核心代码实现从请求到存储的完整实战4.1 单页数据提取从 HTTP 请求到结构化数据现在开始写代码。新建一个 Python 文件按自上而下的顺序组织代码。第一步先把所有需要用到的库导入进来import requests from bs4 import BeautifulSoup import time import csv import re然后定义请求头。这一节我们先只爬第一页把逻辑跑通再扩展到全部页面。headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } url https://movie.douban.com/top250?start0filter resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 html resp.text这里有三处细节值得解释一下。第一timeout10给请求设置了一个 10 秒的超时上限避免网络异常时程序无限期卡住。第二resp.encoding utf-8强制指定响应使用 UTF-8 解码。豆瓣页面默认就是这个编码但某些场景下 requests 可能误判编码导致乱码显式指定是最稳妥的做法。第三判断请求是否成功可以用resp.status_code 200来确认但我在实际项目中通常只检查这一步后续解析出错也能在提取阶段暴露问题。拿到 HTML 之后交给 BeautifulSoup 解析soup BeautifulSoup(html, lxml) items soup.select(div.item) print(len(items))select方法接受一个 CSS 选择器这里div.item的意思是所有 class 为 item 的 div 标签。如果一切正常你应该看到输出 25说明一页的数据都抓到了。这时候恭喜你最难的网络请求环节已经跑通剩下的都是体力活。接下来遍历items提取每一部电影的核心信息。我第一次写这段代码时习惯使用find和find_all这种 BeautifulSoup 自带的方法但熟悉了select之后我会更推荐它因为浏览器开发者工具里复制的就是 CSS 选择器两边能无缝衔接。for item in items: rank item.select_one(.pic em).text.strip() title item.select_one(.hd .title).text.strip() rating item.select_one(.rating_num).text.strip() quote_tag item.select_one(.quote .inq) quote quote_tag.text.strip() if quote_tag else meta_str item.select_one(.bd p).text.strip() year re.search(r\d{4}, meta_str).group() if re.search(r\d{4}, meta_str) else print(rank, title, rating, quote, year)单个字段的选择器含义并不复杂.pic em表示 class 为 pic 的元素内部的 em 标签.hd .title是嵌套关系.quote .inq中间的空格表示后代选择器。最关键的一个技巧是quote字段的处理因为不是每部电影都有经典台词如果直接调用.text遇到缺失元素时程序会报AttributeError。所以先判断这个元素是否存在再取值赋默认值。同样年份字段我先从meta_str里拿到了整段1994 / 美国 / 犯罪 剧情这种元信息字符串再用正则\d{4}找出第一个四位数年份。这个提取方式在 Top250 页面上足够稳定因为它格式非常统一。运行这个循环你应该能在控制台看到每部电影的排名、片名、评分等数据证明第一步解析已经成功。4.2 翻页循环把单页逻辑扩展成完整爬虫单页逻辑跑通后我们把代码升级成一个完整的爬虫。核心思路是用range(0, 250, 25)生成 0、25、50 一直到 225 这十个起始值每次循环构造一个 URL然后重复单页解析流程。这里我直接把所有电影的数据汇总到一个大列表里all_movies [] for start in range(0, 250, 25): url fhttps://movie.douban.com/top250?start{start}filter resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, lxml) items soup.select(div.item) for item in items: rank item.select_one(.pic em).text.strip() title item.select_one(.hd .title).text.strip() rating item.select_one(.rating_num).text.strip() quote_tag item.select_one(.quote .inq) quote quote_tag.text.strip() if quote_tag else meta_str item.select_one(.bd p).text.strip() year_match re.search(r\d{4}, meta_str) year year_match.group() if year_match else all_movies.append({ rank: rank, title: title, rating: rating, quote: quote, year: year, }) time.sleep(2) print(f共爬取 {len(all_movies)} 条数据)需要注意time.sleep(2)这是每抓完一页之后暂停两秒再抓下一页。这一步在初始阶段会让人觉得多此一举慢死了但它是保证爬虫能长期稳定运行的关键策略。你一定不想体验爬到第三页结果发现 IP 被限制的滋味——那种情况下一整个爬虫脚本全都白跑了还得等很久才能恢复。所以我建议你从一开始就养成加延时的习惯这不是代码的累赘是对服务器最基本的礼貌。如果你更谨慎一些可以用time.sleep(random.uniform(1, 3))来随机化暂停时长让访问节奏看起来更接近真实用户这只需要在导入模块时加一个import random。这种细节在你的项目里也许不重要但当爬虫规模扩大时它就是必修课了。4.3 数据清洗与存储输出标清 CSV 文件数据到手只是第一步能导出成通用格式才能算完成。CSV 是最通用的表格存储格式Excel 和几乎所有数据处理工具都能直接打开。Python 标准库自带csv模块不需要额外安装。写入 CSV 前先处理一个编码问题。如果直接用utf-8编码保存 CSVExcel 打开时会出现中文乱码。解决办法是使用utf-8-sig编码也就是带 BOM 的 UTF-8Excel 会自动识别。这一步纯粹是经验之谈网上很多教程没提读者自己踩了坑才恍然大悟。with open(douban_top250.csv, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[rank, title, rating, quote, year]) writer.writeheader() writer.writerows(all_movies)newline这个参数也是必要的否则在 Windows 环境下写入的 CSV 会在每行之间多处一个空行这就是换行符被二次处理导致的。写完代码跑一遍到项目文件夹里找到douban_top250.csv用 Excel 或者记事本打开你应该能看到 250 行电影数据。如果你对数据可视化和进一步处理感兴趣下一步可以把 CSV 文件用 pandas 读进来统计一下 Top250 中哪个年代的电影最多、哪个国家的产出质量最高。这些都属于对已爬取数据的合法使用整体流程就是爬虫、存储、分析、应用。4.4 完整代码汇总为了让你方便对照我把去掉注释的完整版代码整理如下。这里的代码结构和前面分步讲解时一致只是把输出到 CSV 的部分合并进来了import requests import re import time import csv from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } all_movies [] for start in range(0, 250, 25): url fhttps://movie.douban.com/top250?start{start}filter resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, lxml) for item in soup.select(div.item): rank item.select_one(.pic em).text.strip() title item.select_one(.hd .title).text.strip() rating item.select_one(.rating_num).text.strip() quote_tag item.select_one(.quote .inq) quote quote_tag.text.strip() if quote_tag else meta_str item.select_one(.bd p).text.strip() year_match re.search(r\d{4}, meta_str) year year_match.group() if year_match else all_movies.append({ rank: rank, title: title, rating: rating, quote: quote, year: year, }) time.sleep(2) with open(douban_top250.csv, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[rank, title, rating, quote, year]) writer.writeheader() writer.writerows(all_movies) print(爬取完成共写入, len(all_movies), 条数据)保存这个文件在终端运行python 文件名.py耐心等待大约半分钟主要是延时时间然后看看输出结果。如果你能顺利运行到这里恭喜你你的第一个完整爬虫项目已经完成了。5. 常见问题排查与经验心得5.1 高频报错对照速查表每个初学者都会在不同的环节遇到问题我把这几年带新人时最高频的几个报错汇总成一张速查表你碰到时可以对着排查症状根因解决办法请求返回 418 或 403缺少 User-Agent或被识别为爬虫在请求头里补充完整的浏览器标识返回的 HTML 长度极短被反爬机制拦截返回了验证页降低请求频率随机化延时时间中文乱码编码解析错误显式指定resp.encoding utf-8AttributeError: NoneType object has no attribute text某个元素未找到通常是字段缺失先用 if 判断元素是否存在再取 textCSV 中每行之间有空行写入时未指定newline打开文件时加上newline参数只爬到了 25 条而非 250 条翻页循环写错或未设置 range 循环检查循环次数和 start 参数是否递增 25关于第一个问题我再多说一点。有些网站对爬虫的拦截手段非常严格会看你的请求频率、看你的 Cookie、看你的浏览器指纹仅仅加一个User-Agent也未必能通过。但豆瓣 Top250 的防护强度属于中等水平在日常频率下加个浏览器标识基本就够了。如果哪天你遇到了 418 状态码先检查一下是不是请求太快而不是急着去研究高端伪装方案。5.2 爬虫运行太慢延迟与效率的平衡总有人问我time.sleep(2)是不是太慢能不能直接删掉、瞬间爬完我的回答是可以删但你要做好被限制访问的心理准备。服务器有反爬机制一旦它识别出你在高速大量请求轻则返回验证码让你过不去重则直接封禁你的 IP 一段时间到时候哭都来不及。那如果数据量大、又需要快怎么办我的建议是把延时降到time.sleep(0.5)到time.sleep(random.uniform(0.5, 1.5))这个区间这个节奏对豆瓣 Top250 这种规模的项目完全够用了。再快的速度就需要更复杂的并发控制方案比如用异步请求但那是进阶话题对一个 250 条数据的项目来说完全没必要。我见过不少初学者在代码里删掉延迟后前三页爬得飞快第四页开始被拦截然后折腾半天研究反爬策略。说句实话这属于典型的因小失大。爬虫的本质是模拟真人访问真人点下一页之间至少也要停顿几秒你机器人的行为如果比真人还快被识别是理所当然的事。5.3 断点续爬爬取中断后不用从头再来如果你在爬比较大的项目时中断了比如爬了 1000 条突然网断了重新跑一遍实在浪费时间。虽然本文的 Top250 项目只有 10 页不涉及这个问题但我还是想分享这个思路因为它对你以后写大规模爬虫非常有价值。方案一是记录进度把已经成功抓取的页码存到一个本地文件里下次运行时先读取这个文件跳过已完成的部分。方案二是设置start参数直接从断点继续。比如你知道第 5 页出了问题那下次运行就从start100开始。这两种方式本质都是可恢复的爬虫核心区别在于进度信息的存储位置。在我的实际项目中我喜欢把进度信息写成 JSON 文件因为 JSON 结构简单、跨语言通用。但如果是爬的数据量非常巨大建议直接用数据库用一张表记录哪一页抓过了、抓取状态如何这样后面出现问题可以精准定位是哪一页的哪条数据出了问题。这些都属于经验的范畴你经历几次中断后就自然会有感悟了。6. 爬虫的合规边界与正确打开方式6.1 robots.txt先看规则再动手每个正规网站都会在根目录下放一个robots.txt文件用来声明哪些路径允许搜索引擎和爬虫访问。这是一个君子协议没有法律强制力但尊重它既是对对方服务器的考虑也是保护自己的最佳策略。豆瓣的robots.txt地址是https://movie.douban.com/robots.txt你可以用浏览器打开看看。它里面明确写了一些不允许访问的路径比如/subject_search、/service/等但Top250这个路径并不在禁止列表中。这说明以学习为目的、以正常频率访问 Top250 页面是符合对方规则的行为。理解robots.txt有一个常见误区它允许你访问不代表你可以随意折腾。就像你去朋友家做客朋友说欢迎来玩不等于欢迎把冰箱搬空。访问频率、访问深度、是否商用这些都是你需要自己把控的边界。6.2 什么样的爬虫姿势是安全的这里我分享一下个人判断标准供你参考。我的原则可以概括为三不一控制不商用、不破坏、不泄露个人隐私控制访问频率。不商用很好理解爬下来的数据不能拿去做收费服务或商业分析报告。不破坏是指不要爬取需要登录才能看到的、不在公开页面上显示的内容更不要用并发请求冲击对方服务器。不泄露是指爬虫涉及用户个人信息时必须格外谨慎但 Top250 这种公开数据本身没有这个风险。控制访问频率这个点再怎么强调都不过分。我给自己的标准通常是单线程加延时把访问节奏控制在人类点击的合理范围内。如果你发现自己需要在短时间内抓取大量数据最合理的做法不是去研究如何伪装得更好而是去查对方是否提供了公开的 API 或者开放数据接口。正规的数据获取方式永远优先于脆弱的爬取方式。6.3 爬完数据还能做什么爬虫本身只是手段数据才是目的。250 条豆瓣高分电影数据在手你可以做的事情其实很多。如果你对数据分析感兴趣可以用 pandas 读取 CSV统计 Top250 中各年份的上榜数量你会发现上世纪九十年代到新世纪初期是豆瓣高分电影的黄金年代。你还可以按国家统计榜单构成分析不同地区电影的风格差异。如果你对数据可视化感兴趣可以用 pyecharts 或 matplotlib 做一个柱状图展示不同国家在榜单中的占比。这些都是纯粹的个人学习和研究用途安全合规而且对提升综合能力很有帮助。如果愿意再往前一步你可以把 CSV 数据导入一个新项目里做一个交互式电影推荐表输入一个年份就列出对应的所有高分电影。这已经是一个完整的个人项目闭环了数据获取、数据清洗、数据应用。学到的东西远不止爬虫本身。7. 进阶之路从入门到合格的爬虫开发者7.1 从静态页面到动态加载接口豆瓣 Top250 是静态页面请求一个 URL 就能拿到全部数据这在爬虫领域算是最简单的模式。但今天很多主流网站都已经改成了前后端分离架构网页先返回一个空壳 HTML真正的数据靠 JavaScript 异步请求接口获取。这种情况下用 requests 直接请求页面 URL 得到的是空壳什么都拿不到。检查的手段是打开浏览器开发者工具切到 Network 标签页刷新页面并观察 XHR 类型的请求。你会在里面找到一个返回 JSON 数据的接口后续的数据提取就变成了解析 JSON 而非解析 HTML。这个进阶方向本身不难但很多新手没有这个概念卡在为什么我爬到的 HTML 里没有数据这个问题上很久。建议你在学完本文的静态页面爬虫后找几个前后端分离的网站练练手重点学会 Network 面板的使用方法。7.2 让爬虫更健壮异常处理与请求重试本文的代码为简洁起见没有做异常处理但真实场景中网络抖动、服务器暂时不可用非常常见。如果你的脚本没有异常捕获一个小波动就会导致整个爬虫中断在页与页之间。一个成熟的爬虫应该在每次请求外加上try...except捕获超时和连接异常并做重试。我自己的经验是每次请求最多重试 3 次每次重试之间间隔 2 秒以上如果仍然失败就记录日志跳过这一页。重试多于 3 次通常也没意义因为连续多次失败说明问题不在网络瞬断很可能是你的访问行为触发了反爬策略这时候再重试只会雪上加霜。def fetch_page(url, headers, retries3): for i in range(retries): try: resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 return resp except requests.RequestException as e: print(f请求失败第 {i1} 次重试{e}) time.sleep(2) return None这个函数本身就可以在你以后的爬虫项目里反复复用建议收藏到你的代码工具箱里。7.3 更多存储方案从 CSV 到数据库当数据量从几百条增长到几万条时CSV 文件就不再够用了。查询、去重、更新都会变得很费劲。这时候数据库是更好的选择。SQLite 是最轻量的数据库解决方案不需要安装额外的数据库服务Python 标准库直接支持。你可以在爬虫代码里连接 SQLite把数据插入一张movies表。后续再用 pandas 或 SQL 查询语句做统计比操作 CSV 靠谱得多。等到数据量进一步增长或者需要多人同时访问时才需要考虑 MySQL 或 PostgreSQL 这类完整的数据库服务。迁移到数据库并不意味着完全抛弃 CSV。很多时候我会把爬到的原始数据留一份 CSV 作为备份再把整理后的结构化数据放数据库。备份和易用性都照顾到了。7.4 一步到位的异步爬虫是什么样最后聊一聊异步爬虫。异步不是更快而是更高效地利用等待时间。同步爬虫发出一个请求后CPU 就在那干等服务器响应异步爬虫可以在等待的同时去请求下一个页面网络 IO 和 CPU 时间被充分复用整体速度能成倍提升。Python 里常用的异步爬虫框架是 aiohttp。但需要强调的是速度快了之后更要遵守访问纪律异步并发两三倍的请求量和十倍的请求量对服务器的压力天差地别。如果你刚开始学爬虫我建议先把同步版本玩熟、把网页解析的技能练扎实再考虑异步方案。异步带来的性能收益在你爬几千条数据之前是感知不到的。我在实际项目中发现爬虫技术成长最快的方式不是看更多理论而是亲手去爬三五个不同类型的网站然后不断优化自己的代码。静态页面、动态页面、需要模拟登录的页面每一种都是一层新的认知。你可以把这项技能理解成一种搜索引擎能力的外挂它让你能拿到公开可用的数据去做自己的分析、验证自己的想法。最后再分享一个小技巧写爬虫时很多问题本质上是选择器写得不对与其反复改代码猜结构不如在浏览器控制台里直接试验一下你的 CSS 选择器是否匹配到了目标元素。先用document.querySelector验证选择器再把验证过的选择器搬进 Python 代码这样定位问题的速度会快很多。踩过几次坑之后你会发现写爬虫最花时间的往往是定位细节而一旦你把 HTML 的层级结构摸清楚后面的数据处理反而顺手得让人上瘾。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进