ARTICLE DETAIL

资讯详情

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

用MIT新闻练手:Python爬虫实战入门全攻略

用MIT新闻练手:Python爬虫实战入门全攻略 1. 为什么我推荐用“麻省理工新闻”练手——初级爬虫的第一个靶场1.1 一个爬虫老手挑目标的三个标准我带过不少朋友入坑爬虫大家最爱问的问题不是“requests怎么用”而是“我该先拿什么东西练手”。有人一上来就挑电商网站结果被验证码和滑块折磨得怀疑人生有人去爬社交平台发现全是JS渲染的数据半天取不到几行还有人盯着政府公开数据接口虽然能跑通但总觉得没学到页面解析的东西。按我的经验给初级爬虫选目标网站至少得满足三个条件第一页面结构要足够规矩不搞那些花里胡哨的动态渲染确保你用requests拿到HTML之后真的能从中解析出东西第二数据规模要合适不能太小让你觉得像在背答案也不能大到把简单事情搞复杂第三站点要稳定别爬两页就封你IP或者直接给你返回一堆乱码。麻省理工学院新闻MIT News就是这样一个很理想的起点。它看起来像个学术机构网站但本质上是一个结构非常清晰的新闻门户有列表页、有分页、有详情页、有多条新闻条目每个条目里又包含标题、发布时间、摘要、链接、分类。这种结构几乎覆盖了爬虫入门阶段百分之八十的核心场景。1.2 MIT新闻这个目标里藏着哪些入门点先说这个网站的技术栈。打开麻省理工新闻的任意列表页你按下F12看到的DOM结构是服务端直接渲染出来的这意味着什么意味着你不需要用Selenium不需要等浏览器执行JavaScript只需要一个requests.get()拿回HTML源码就能在里面找到所有想要的数据。这一步直接省掉了初级爬虫最大的一个坑——处理动态加载。再说它适合练习的理由这个站点的XPath结构稳定。我试过在不同分类页、不同分页之间切换文章卡片区域的class命名虽然有一定层次但相对固定一旦你写好了第一页的解析规则后面几十页基本可以复用。你还可以顺手练一练XPath的text函数、contains函数、节点轴这些操作而不是像某些网站那样每条数据的XPath都长得完全不一样。更重要的是MIT新闻的数据量适中。新闻网站一般有持续更新的内容分页数量可观又不至于多到离谱。你练习循环翻页、拼接URL、处理最后一页空列表这些问题时都能在一个比较可控的范围内完成。如果你想练到分布式爬虫、消息队列、调度框架那一步也可以拿它的全站文章做数据源。所以这个目标并不是练完就丢的玩具而是一个从入门到进阶都能用的数据来源。我个人的建议是不要一上来就追求“爬到全站几十万条数据”而是先把一个列表页、一个详情页的解析做到烂熟。MIT新闻刚好允许你用最小成本把这件事做扎实。2. 动手前的准备环境、工具和基本思路2.1 需要的Python库只有三个很多初学者容易被各种框架绕晕。今天听人说Scrapy好明天听人说要用Playwright后天又有人说必须上代理池——这些都没错但都不是你第一天的任务。做初级爬虫工具能少就少先把基本功打牢。这次实战我建议你只用三个库requests负责发HTTP请求拿回网页源码。它是Python生态里最常用的HTTP库比urllib好用太多。lxml负责解析HTML配合XPath提取节点。lxml底层是C语言写的libxml2解析速度快写XPath也方便。csv / jsonPython标准库负责把结果落盘。数据量不大时根本不需要上数据库CSV文件足够你后续做分析和练习。有一些教程会推荐你用BeautifulSoup requests的组合。我承认BeautifulSoup的API对新手更友好但它在某些场景下的解析速度明显不如lxml而且xpath的表达式写熟了之后代码的可维护性会比find_all那一套更高。你可以两种都试不过这次我以lxml为主因为你想在网上搜“python xpath爬虫 text函数”这类问题时大部分答案基本都是基于lxml写法的。安装环境的时候如果你还没装Python建议直接用Python 3.10以上的版本。然后执行pip install requests lxml就这么简单。不要在这步犹豫太久真正的难点在后面的解析逻辑上。2.2 解析方案怎么选正则、XPath还是CSS选择器新手最容易犯的一个错误是想用正则表达式解析一切。正则确实强大但用正则去匹配HTML结构非常脆弱一旦网页的标签顺序有微调或者属性值里多了个空格你的规则就可能全部失效。我在早期爬虫经验里最大的教训之一就是妄图用一段正则搞定整页新闻内容结果页面一改版就躺平。比较合理的选择是XPath或CSS选择器。XPath的优势在于它操作的是文档树你能明确表达“选取某个div下所有具有某class的a标签”这种层级关系HTML的标签变化对它的影响远小于正则。CSS选择器在BeautifulSoup或者Selenium里用得多写法更简洁但在处理复杂的兄弟节点、父节点、文本筛选时没有XPath那么灵活。所以这次的代码统一用lxml.etree配合XPath来完成。如果你在搜索引擎里看到类似“python xpath爬虫 text函数”的内容核心其实就是用text()去匹配节点的文本用contains()去模糊匹配属性值。这些都会在当前这个项目里用到。2.3 先摸清网页结构再写代码写爬虫最容易犯的另一个错误是一上来就开IDE敲代码敲了半天才发现选择器压根不命中。正确顺序是先打开浏览器按F12看一眼页面结构再用代码去验证。打开MIT News的首页你会看到类似这样的结构逻辑页面上方是新闻列表每条新闻被包裹在一个列表项中列表项里面有标题链接标题下方有摘要文字再往下是发布日期和分类标签。列表底部则是分页控件包括首页、末页、上一页、下一页以及页数链接。我在实际开发中还有一个习惯在浏览器里右键某个标题选择“检查”然后复制它的XPath先放到Python里跑一次看能不能取到。这样可以快速验证页面结构是否是你理解的那样。这个步骤虽然不起眼但能省掉后面大量的调试时间。3. 核心实战用requests把整站文章标题拉下来3.1 第一步请求页面并验证响应我们的第一个目标是把MIT新闻的第一页文章标题全部提取出来并打印在控制台上。这一步虽然简单但它把请求、解析、提取三个核心环节完整地串了一遍。先上基础代码import requests from lxml import etree url https://news.mit.edu/news2 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) print(resp.status_code) print(resp.encoding)为什么非要在请求头里带User-Agent因为很多服务器会对没有UA的请求直接返回403或者识别为异常客户端。浏览器访问页面时会自动携带这一串信息你的爬虫如果空着手过去服务器当然怀疑你是脚本。加上这个常见的UA至少能让请求看起来更像真人操作。请求完之后记得检查resp.status_code。如果是200恭喜你可以继续解析如果是404说明URL写错了如果是403大概率被封或者需要更多请求头如果是5xx那就是服务器自己的问题。这里我给初学者的建议是出现问题先打印状态码和响应文本的前500个字符看看返回的到底是什么而不是盲目调代码。3.2 第二步用XPath定位文章标题请求成功后接下来就是关键的解析环节。用lxml解析HTML字符串然后写XPath提取标题。html etree.HTML(resp.text) # 观察页面结构后发现标题在 h3 下的 a 标签里 titles html.xpath(//h3[classnews3__card-title]/a)[^1] 注意不同的时间点访问MIT News页面结构有可能调整。比如我这次看到的class名是news3__card-title你下次访问时可能已经改版。所以这里我要强调XPath里最稳妥的写法是尽量结合标签语义和class特征但也不要完全依赖class。下方是一个更通用的写法titles html.xpath(//article//h3/a | //div[contains(class,title)]//a)当你在网上搜索XPath的text函数时常见的需求是“根据链接文本筛选节点”。比如只提取文本里包含“MIT”的标题targets html.xpath(//h3[contains(a/text(), MIT)]/a)这里用contains和一个函数的组合就是XPath里非常典型的文本筛选技巧。先不急着用太复杂的规则你只需要记住XPath里的方括号是条件过滤text()代表当前节点的文本contains()是模糊匹配的工具这三个东西组合起来能解决绝大多数文本定位问题。3.3 第三步加上分页循环拿到全站列表单页解析只是热身。爬虫真正的价值在于“批量”。MIT News的列表页URL有一定规律通过观察分页控件的链接你会发现它用类似“?page2”这样的查询参数。那写起来就很简单了。import time all_titles [] for page in range(1, 11): url fhttps://news.mit.edu/news2?page{page} resp requests.get(url, headersheaders, timeout10) if resp.status_code ! 200: break html etree.HTML(resp.text) titles html.xpath(//h3[contains(class,title)]/a/text()) if not titles: break all_titles.extend(titles) print(f第{page}页抓到{len(titles)}条标题) time.sleep(1)这段代码里有几个细节值得注意。第一每次请求之间加了time.sleep(1)这是给服务器喘息的空间也是爬虫的基本素养。第二如果在某一页抓不到标题就主动break避免因为页面结构变化导致无限空转。第三用extend而不是append把列表平铺而不是嵌套进一个大列表。你可能想问为什么非要睡眠实际上如果你用全速去抓取一个新闻网站几百个请求瞬间打过去很容易触发服务器的限流策略。我在实际操作中发现即使是相对友好的新闻站也不喜欢被密集请求刷。初级爬虫阶段养成加延迟的习惯比以后被封锁再来补课划算得多。3.4 保存到CSV并加上基本异常处理数据抓下来不保存等于白干。用Python内置csv库把这些标题写入文件第一步实战就算完整了。import csv with open(mit_titles.csv, w, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerow([标题]) for title in all_titles: writer.writerow([title])加上异常处理以后代码会更健壮try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() except requests.exceptions.RequestException as e: print(请求失败:, e) continue这里用resp.raise_for_status()代替手动判断只要状态码不是2xx就会主动抛出异常省得你每页都写一个if判断。你可能会在实际运行中遇到连接超时、DNS解析失败、SSL证书校验失败等各种问题requests的RequestException就是这些错误的总父类统一捕获能防止程序因为一次网络抖动就整个崩溃。到这一步你已经完成了一个最基础的爬虫闭环发送请求、解析HTML、提取数据、批量翻页、落盘保存。这五个环节就是爬虫技术的核心骨架后面所有复杂的框架和中间件都是在这个骨架上做增强。4. 进阶细节提取发布时间、摘要与正文链接4.1 解析列表页里能挖到的所有信息光有标题不够。新闻网站的数据维度至少还包括发布时间、摘要、分类、作者、原文链接。如果你只抓标题后面做内容分析或者训练语料时会发现信息量根本不够用。MIT News列表页的结构比较友好每条新闻卡片里同时包含标题、摘要、发布时间和链接。对应的XPath可以逐个拆解。以下是我在解析时实际用过的写法cards html.xpath(//div[contains(class,view-content)]//article) for card in cards: title card.xpath(.//h3/a/text())[0].strip() link card.xpath(.//h3/a/href)[0] summary card.xpath(.//div[contains(class,description)]/text()) date card.xpath(.//time/datetime)这里的一个关键技巧是“先定位卡片再在卡片内做二次定位”。也就是说不要一开始就写绝对路径把整条XPath从html/body一路写到文章卡片那样改版一次就废掉。用.//开头意思是“从当前节点的后代里查找”代码的容错性会明显提高。有些新闻网站的发布时间不是显示成标签而是藏在文本节点里比如“March 12, 2025”。这时候你就得用XPath里的text函数去提取date_text card.xpath(.//span[contains(class,date)]/text())如果多个节点拼在一起你可以用string-join或者直接用Python侧做replace清洗。记住一个原则XPath负责定位Python负责清洗。别试图用一条巨大无比的正则或XPath把所有脏东西都处理完拆开来做每个环节都好调试。4.2 追进详情页拿正文和图片列表页信息毕竟有限正文和图片需要进入详情页抓取。这一步会引入一个新的问题请求量从一页几十个变成几十页几百个。你需要控制节奏也需要注意异常处理。详情页的解析思路和列表页一致先定位正文容器再从中提取段落文本。detail_resp requests.get(link, headersheaders, timeout10) detail_html etree.HTML(detail_resp.text) paragraphs detail_html.xpath(//div[contains(class,article-body)]//p/text()) full_text \n.join(p.strip() for p in paragraphs)图片同样可以提取images detail_html.xpath(//figure//img/src)但这里需要注意MIT News的图片地址有时候是相对路径你得自己拼上域名前缀。还有图片可能是srcset或者data-src这种懒加载属性直接取src可能取不到。初级爬虫阶段遇到这种情况你可以先不做图片下载只记录图片URL后续需要再研究懒加载的规律。追进详情页时我强烈建议你打印一下进度比如当前抓到哪个链接、标题是什么、正文有几千字。别小看这种日志它会帮你在程序跑挂的时候迅速定位问题出在哪条URL上。4.3 优雅限制请求频率给自己的爬虫装个“刹车”既然已经进入了详情页抓取阶段请求量就上来了。这时我建议你把请求封装成一个函数在函数内部统一管理请求间隔和异常重试。def fetch(url, retry3, delay1): for i in range(retry): try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() return resp except requests.exceptions.RequestException as e: print(f第{i1}次请求失败: {e}) time.sleep(delay) return None这个函数的好处是你所有的请求逻辑都集中在一个地方后续想加上代理、想修改重试策略、想统一设置请求头只需要改这一个函数。我自己的项目里几乎所有基于requests写的爬虫最后都会长成这样。另外关于延迟有一个小经验固定延迟1秒看起来简单但机器味很重。如果想更自然可以加一点随机浮动import random time.sleep(0.5 random.random())这样做不是为了让服务器“看不出来”而是为了平滑请求流量避免瞬时脉冲。5. 常见问题与排查技巧实录5.1 返回值404/403怎么处理我见过太多新手在爬虫报错时不知所措。先说404它通常是URL拼接错了尤其是分页参数。你可以在代码里打印当前请求的完整URL然后复制到浏览器里打开看看。如果浏览器能正常打开但requests返回404那就是URL里带了什么特殊字符或者参数被错误编码了。403比404更麻烦意味着服务器知道你是爬虫或者至少不愿意响应这次请求。处理思路一般有三步检查User-Agent是否伪装成浏览器还缺少什么常见的请求头可以加上Accept、Accept-Language这些。放慢速度加长sleep时间观察是否恢复正常。如果必须继续访问考虑使用代理IP。但麻省理工新闻这类学术新闻站风格相对温和基本只需要一个正常的UA和适度的请求间隔不太会走到代理那一步。这也是我推荐它做靶场的原因之一——你不需要把精力花在对抗反爬虫上而是专注在爬虫本身的技术学习。5.2 XPath匹配为空怎么定位XPath匹配为空是这个项目里最高频的问题。先别急着改代码按照下面的顺序排查打印resp.text的前2000个字符确认返回的是不是你预期的HTML。用浏览器开发者工具查看目标元素的真实XPath。检查有没有iframe嵌套内容可能在iframe里常规XPath根本触碰不到。检查text()和href的提取位置是不是把文本和属性搞混了。我在实际调试中喜欢写一个临时小函数专门用来测试XPathdef test_xpath(html_text, expr): tree etree.HTML(html_text) result tree.xpath(expr) print(len(result)) for item in result[:3]: print(item)每次调整XPath后直接调用这个函数看能不能命中。命中后再放进完整的爬虫流程里能极大减少“写了半天才发现选择器错了”的情况。5.3 虚假的“动态加载”如何识别真正的JS渲染页面MIT News是服务端渲染所以requests能直接拿到内容。但你会看到很多其他网站requests拿回来的HTML里根本没有你要的数据而是在一个空的div里等着JavaScript去填空。判断方法很简单在浏览器开发者工具里查看页面源代码也就是CtrlU那个原始HTML搜一下你的目标文本。如果原始HTML里能搜到那是服务端渲染requests可以直接拿如果搜不到那大概率是客户端渲染需要Selenium或者Playwright这类工具驱动浏览器。很多初级爬虫的问题在于分不清这两者。看到某些教程说“不需要seleniumrequestsAJA抓动态数据”就以为自己也能行。实际上那种做法需要去翻XHR接口、解析JSON属于另一套技术栈。MIT News的好处就是它没有这个问题你先练好一套技术再接触另一套才不会混。5.4 编码问题新闻页里的UTF-8和BOM陷阱爬虫第二常见的问题是乱码。MIT News的页面是UTF-8编码requests通常能正确解码。但你在保存CSV时如果直接open一个文件然后往里写中文Windows默认编码可能是gbk导致文件打开乱码。所以写CSV时一定要显式指定encodingutf-8或encodingutf-8-sig。这里有个细节如果文件要用Excel打开建议用utf-8-sig它会自动写入BOM头Excel才不会把它当成乱码。如果你的CSV需要被程序再读取那普通utf-8就够了。还有如果你发现页面返回的内容解析出来有奇怪的字符可以手动检查resp.encoding是什么必要时用resp.encoding utf-8强制指定。requests有时候会根据headers里的charset猜编码猜错就会乱手动指定是最可靠的。6. 从“能跑”到“好用”的一些经验心得6.1 爬虫速度适度并发带来的收益当你把单线程爬虫跑通之后会开始嫌弃它慢。MIT News全站可能有几万条新闻每条详情页请求一次算下来要好几个小时。这时你可以尝试用concurrent.futures里的ThreadPoolExecutor做简单并发。我提醒一句并发不是越多越好。很多新手一上来就开50个线程结果没跑多久就被服务器限制甚至把自己的IP拉黑。以MIT News的速度我个人建议从5个线程开始测试观察请求的成功率和响应时间再慢慢往上加。from concurrent.futures import ThreadPoolExecutor def crawl_detail(link): resp fetch(link) # 解析逻辑 return data with ThreadPoolExecutor(max_workers5) as executor: results list(executor.map(crawl_detail, links))这个并发版本的代码并不复杂但带来的收益很明显。同样是抓几千条详情页单线程可能需要一个小时5线程并发可能缩减到十五分钟。不过你要记住并发场景下的异常处理比单线程更麻烦连接超时的概率会上升日志必须打印线程编号或者URL方便定位问题。6.2 数据清洗与去重爬虫抓回来的数据不能直接用。你会发现标题里有换行、多空格、HTML实体符号摘要里可能有“Read More”之类的尾巴。清洗的基本原则是先把文本strip去掉首尾空白再把不必要的换行替换成空格最后把HTML实体比如 替换成正常字符。这一步用Python的replace配合html.unescape就够。去重也很重要尤其是新闻网站首页和列表页可能有重复推荐位。去重最简单的方式是维护一个已见链接的集合seen set() if link not in seen: seen.add(link) # 处理这条数据这个集合在内存里就够数据量大了再考虑用Redis或者布隆过滤器但你暂时用不上。6.3 它对后续技术栈的延展价值我认为麻省理工新闻这个爬虫项目最大的价值是它的数据结构和你的爬虫能力能平滑扩展。你把列表页解析和详情页解析做完之后很自然地就会想标题能不能用来做关键词提取摘要能不能做文本分类正文能不能做主题建模这些下游需求会反过来推动你优化数据格式而不会像某些网站数据那样千辛万苦抓出来却根本没法用。如果你还想往分布式爬虫方向发展MIT News的数据量也算合适的试验田。Scrapy内置的并发调度、去重队列、增量爬取都可以用这个网站当靶子。换句话说这个项目不是一条死胡同而是一个能不断扩展的地基。6.4 合规意识千万别碰哪几类目标最后说点实在的。爬虫技术本身没有任何问题但在实际使用中你必须对目标网站有基本的判断。第一类需要登录后才能访问的数据不要去硬撞。登录墙本质上是网站设定的权限边界你绕过它就是绕过访问控制。第二类明确在robots.txt里禁止爬取的部分至少要有合理频率和完整标识不要踩线。第三类带有个人隐私信息的数据比如手机号、住址、证件号这种不管技术上容易还是难都不该碰。对MIT News这类新闻网站适度抓取公开的新闻标题和摘要用于学习或数据分析风险相对可控。但我也建议你在代码里写明当前请求的目的设置可辨识的User-Agent比如加入你的联系方式或项目名。这既是礼貌也是一层自我保护。爬虫最宝贵的能力不是“能把网页抓下来”而是“知道自己能抓什么、该抓什么”。这段经验在我参与的项目里反复被验证希望你看完这篇实战记录后也能先想清楚目标再动手写第一行代码。
返回列表