ARTICLE DETAIL

资讯详情

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

Python爬虫入门实战:从requests到BeautifulSoup完成数据抓取

Python爬虫入门实战:从requests到BeautifulSoup完成数据抓取 带过不少刚开始学Python的朋友我发现一个挺有意思的规律最容易放弃的时间点不是第一周被语法劝退而是语法学得差不多的时候突然不知道拿这门语言干什么。写“学生管理系统”吧感觉没什么用看开源项目吧又看不懂。这种“知识真空感”才是真正的劝退主力。所以这个系列安排到day14这天我特意没有继续堆语法而是专门用一整篇带你做一个真实能用的程序——网页爬虫。爬虫这个东西准确地说就是让程序替我们自动去网页上抓取数据。它不像“Hello World”那样只是验证环境它会用到前面13天学过的几乎所有知识变量存数据、循环翻页、函数封装逻辑、异常处理兜底、文件操作落盘。把这一套组合拳打下来你学过的语法才算真正变成自己的武器。这篇内容也适合那些基础语法学完、想找第一个实战项目练手的朋友。1. 为什么第14天这个节点最适合动手写爬虫先说个很多人会踩的误区学编程想立刻搞个项目结果一上来就听说爬虫很火就直接去抄那种几十行抓淘宝评论的代码。抄完发现全是JSON加密、签名算法、人工验证码根本跑不通。这不是你的问题是选题超出了当前阶段。1.1 前13天打下的底子恰好够用我习惯把入门阶段的知识点拆成几块变量和数据类型、流程控制if/for/while、函数、文件读写、异常处理、模块导入、类和对象。前13天我们做的就是把这些内容一个个过完。这些知识和爬虫之间是什么关系我列一下你就明白了变量和列表存储抓下来的文本、作者、标签for循环遍历一页里的所有数据块以及循环翻页if判断检查状态码是否是200判断页面是否抓取成功函数把“请求网页”和“解析网页”拆成独立单元异常处理网络超时、连接被拒时的兜底逻辑文件操作把结果写入CSV或Excel模块导入requests、BeautifulSoup这些第三方库爬虫说是新知识其实核心就两件事发请求拿到HTML再从HTML里提取目标数据。发请求靠requests库提取数据靠BeautifulSoup库这两个都是封装得很好的工具。你真正需要发挥的恰恰就是前面13天学过的编程基本功。1.2 这个阶段别碰的东西我也把丑话说在前面。刚学会爬虫基础有些内容暂时别碰不是因为学不会而是性价比太低容易挫伤积极性需要登录才能看到数据的网站涉及session、cookie维持逻辑链长页面数据由JavaScript动态渲染的网站requests拿到的是空壳HTML需要selenium或分析接口带复杂加密参数的移动端App接口这属于逆向工程范畴不是入门期该干的事高频抓取大厂核心业务数据这既容易触发封禁也可能踩法律红线这一篇我选的目标网站是一个专门供爬虫学习者练习的公开站点结构简单、无登录门槛、没有动态渲染非常适合作为第一只“小白鼠”。等这一套流程跑顺了后面的进阶才有意义。2. 环境准备两条命令搞定requests与BeautifulSoup动手之前先把环境收拾干净。我的建议是每个Python项目单独建一个虚拟环境别图省事直接装在全局环境里。虚拟环境这个概念很多新手觉得没必要等到你同时维护两三个项目、每个项目依赖不同版本库的时候就知道它有多重要了。2.1 虚拟环境每个项目一个独立环境别怕麻烦创建虚拟环境的命令很简单python -m venv venv这行命令会在当前目录下生成一个venv文件夹。接下来激活它# macOS / Linux source venv/bin/activate # Windows venv\Scripts\activate激活成功后命令行前面会出现(venv)标记说明你已经在虚拟环境里了。之后安装的任何包都只存在于这个环境不会污染全局Python。如果你执行python命令时提示找不到多半是环境变量没配好。Windows用户在“系统属性-环境变量-Path”里把Python安装目录和Scripts目录加进去或者直接用py -m venv venv调用Python启动器。macOS/Linux用户一般是python3 -m venv venv因为很多系统默认的python指向的是Python 2。2.2 requests三件套get、status_code、text激活环境后安装两个库pip install requests beautifulsoup4装好后先用一个最简单的请求验证环境通不通import requests url http://quotes.toscrape.com/ resp requests.get(url, timeout10) print(resp.status_code) print(resp.text[:300])resp.status_code是HTTP状态码200代表请求正常。resp.text是服务器返回的HTML源码我截取前300个字符确认返回的是预期页面而不是什么错误页。requests库最常用的参数我整理了一张表参数作用示例params拼接URL查询参数requests.get(url, params{page: 2})headers自定义请求头如User-Agentrequests.get(url, headers{User-Agent: Mozilla/5.0})timeout超时秒数防止卡死requests.get(url, timeout10)proxies指定代理按需配置requests.get(url, proxies{http: ...})我习惯每个请求都带上timeout。没有超时限制的爬虫一旦目标服务器连接异常程序可能挂在那一动不动非常耽误事。3. 第一只爬虫抓取名言网页的完整代码拆解练习站点我选了quotes.toscrape.com这个网站每一页显示10条名言每条包含名言内容、作者和标签三个字段HTML结构规整是全世界爬虫教学里最常用的练手站点之一。3.1 完整代码与运行效果先看整段代码不复杂但信息量很足import requests from bs4 import BeautifulSoup url http://quotes.toscrape.com/ resp requests.get(url, timeout10) print(状态码:, resp.status_code) soup BeautifulSoup(resp.text, html.parser) quote_list soup.select(div.quote) for quote in quote_list: text quote.select_one(span.text).get_text() author quote.select_one(small.author).get_text() tags [tag.get_text() for tag in quote.select(a.tag)] print(text, -, author, tags)运行后你会看到类似这样的输出“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.” - Albert Einstein [change, deep-thoughts, thinking, world] “It is our choices, Harry, that show what we truly are, far more than our abilities.” - J.K. Rowling [abilities, choices]就这么十几行代码一个能用的爬虫已经跑起来了。我们来逐行拆一下第一段导入requests和BeautifulSoup。resp requests.get(url, timeout10)发出请求拿到响应对象。关键是BeautifulSoup(resp.text, html.parser)这一行。它把HTML源码解析成一个可搜索的文档树之后你就能用各种方法在这棵树里定位元素了。quote_list soup.select(div.quote)这一步是“找到所有class名为quote的div标签”。select方法支持CSS选择器div.quote表示div标签且class包含quote返回值是一个列表。后面的for循环就顺理成章了对每个quote块分别用select_one提取里面的名言文本、作者和标签。select_one和select的区别在于前者只取匹配的第一个元素后者返回所有匹配的列表。3.2 第一次运行跑出错误的三个常见原因新手第一次跑这个代码报错基本逃不出下面三类第一个ModuleNotFoundError: No module named requests。说白了就是没装requests或者装的时候没装进当前环境。检查一下命令行前面有没有(venv)标记然后重新执行pip install requests beautifulsoup4。这个报错也经常出现在PyCharm里右下角解释器选的是全局环境而你的包装在虚拟环境里两者对不上。解决办法是在PyCharm的Settings里把Project Interpreter切到刚才创建的venv。第二个ConnectionError或超时。说明请求根本没发出去要么是网络问题要么是目标站点暂时不可用。可以先在浏览器里手动打开这个网址确认网站本身没问题再检查代码里URL有没有拼错。第三个AttributeError: NoneType object has no attribute get_text。这个最典型意思是select_one没找到元素返回了None。原因基本是你写的选择器跟网页实际结构对不上。解决方法是打印soup的一小段HTML看看或者回头用浏览器开发者工具重新核对选择器。记住一个调试习惯别猜打出来看。4. HTML解析的核心心法选择器用对效率翻倍爬虫写多了你会发现发请求永远是那几行代码真正花时间的地方全在解析HTML。这一节我把解析的思路和方法讲透。4.1 在浏览器里当“侦探”三分钟定位目标数据拿到一个网页第一步不是在代码里瞎试选择器而是先在浏览器里打开它按下F12进入开发者工具。点击左上角的“选择元素”箭头图标再把鼠标移到页面上你想抓取的内容上右键检查就能看到这段内容对应的HTML结构。以名言练习站为例你选中一条名言会看到类似下面的结构div classquote span classtext“The world...”/span spanby small classauthorAlbert Einstein/small/span div classtags a classtagchange/a a classtagdeep-thoughts/a /div /div看到这个结构该写什么选择器就一目了然了。每个数据块的容器是div.quote名言文本在span.text里作者在small.author里标签是多个a.tag。这和前面代码里的选择器完全对上了。我教新人的时候常说HTML解析的工作八成在浏览器里就完成了。F12看一眼结构比在代码里反复打印调试快得多。4.2 解析方法怎么选函数式 vs 选择器式BeautifulSoup提供了两套查找方法我通常这样分工方法适合场景示例find()/find_all()按标签名、属性精确查找soup.find_all(div, class_quote)select_one()/select()用CSS选择器逻辑更紧凑soup.select(div.quote span.text)find_all在处理复杂属性条件时比较直观比如find_all(a, hrefTrue)可以找到所有带href的链接。而select的优点是写法简洁结构嵌套关系一目了然div.quote span.text表示“quote块里的text类span”。现在CSS选择器是主流我推荐你优先掌握它。另外提一个经典问题为什么不用正则表达式提取正则确实也能干这个事但对HTML这种嵌套结构非常脆弱。网页稍微改个标签属性你的正则就要重写。而BeautifulSoup是先把HTML解析成文档树再按结构定位天然抗页面微调代码可读性也高得多。初学阶段正则只需要能做到精准匹配字符串就够了解析HTML这种活交给专门的库。5. 数据落盘与中文乱码从print到CSV/Excelprint到控制台的数据刷新了就没了没有积累价值。爬虫的最终目的是把数据保存下来所以这一节讲落盘以及一个绕不开的坑——中文乱码。5.1 一行代码解决Excel打开中文乱码把数据写入CSV文件用Python标准库的csv模块就够了import csv rows [ [名言内容, 作者, 标签], [The world as we have created it..., Albert Einstein, change,deep-thoughts], ] with open(quotes.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerows(rows)注意两个细节。第一个open必须加newline参数否则Windows平台上每写一行会多一个空行。第二个编码一定要用utf-8-sig这是新手最容易踩的坑。为什么是utf-8-sig而不是常见的utf-8因为Excel打开CSV文件时默认用ANSI编码去解码UTF-8的文件在Excel里就会乱码成“鐎涙”。utf-8-sig会在文件开头写入一个BOM标记Excel认这个标记就能正确识别为UTF-8。这个坑我当年踩了不止一次后来养成习惯只要是给Excel用的文件一律用utf-8-sig。如果你想把数据导出成真正的Excel格式.xlsx可以用pandasimport pandas as pd df pd.DataFrame(rows, columns[名言内容, 作者, 标签]) df.to_excel(quotes.xlsx, indexFalse)pandas没装的话先pip install pandas openpyxl。注意pandas写xlsx也建议处理中文编码但用的是另一个机制to_excel默认就是正确的通常不会遇到Excel乱码问题。5.2 让爬虫学会翻页for循环包住一切练习站一共10页每页10条。我们把第3节的单页爬虫升级成多页版本核心就是用一个for循环包住请求和解析逻辑再在每页之间暂停一下import csv import time import requests from bs4 import BeautifulSoup all_data [] for page in range(1, 11): url fhttp://quotes.toscrape.com/page/{page}/ print(f正在抓取第 {page} 页...) resp requests.get(url, timeout10) soup BeautifulSoup(resp.text, html.parser) for quote in soup.select(div.quote): text quote.select_one(span.text).get_text() author quote.select_one(small.author).get_text() tags ,.join(tag.get_text() for tag in quote.select(a.tag)) all_data.append([text, author, tags]) time.sleep(1) with open(quotes.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([名言内容, 作者, 标签]) writer.writerows(all_data) print(抓取完成共保存, len(all_data), 条数据)注意fhttp://quotes.toscrape.com/page/{page}/这个写法是f-string格式化字符串page从1到10循环URL就自动变化了。time.sleep(1)是每抓完一页睡1秒给目标服务器留点喘息空间。这个习惯从第一天就要养成后面我会详细说。6. 别让爬虫变成“骚扰器”延时、重试与合规底线爬虫界有个说法大部分网站封IP不是因为技术识别多厉害而是爬虫太没礼貌。服务器只有两兆带宽你每秒发50个请求不封你封谁。这一节讲如何写一个“文明爬虫”。6.1 礼貌爬取三件套延时、超时、重试第一件延时。上面的代码里已经用了time.sleep(1)这就是最简单有效的礼貌。并发框架那些高端玩法不是现在该考虑的老老实实串行加延时对练习项目来说完全够用。第二件超时。requests.get(url, timeout10)不光是防止程序卡死也是避免无意义的连接占着不放。超时时间根据网站响应速度调整一般5到10秒比较合理。第三件重试。网络请求受各种因素影响偶尔失败一次很正常。一个健壮的网络请求应该具备失败重试的能力import random import time import requests USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/119.0 Safari/537.36, ] def fetch_page(url, retries3): headers {User-Agent: random.choice(USER_AGENTS)} for attempt in range(retries): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp.text print(f状态码异常: {resp.status_code}) except requests.RequestException as e: print(f第 {attempt 1} 次请求失败: {e}) time.sleep(2 * (attempt 1)) return None这里有两个细节值得学习。第一个随机User-Agent。有些服务器会检查请求头里的User-Agent如果发现是Python的默认值可能直接拒绝。伪装成主流浏览器标识是爬虫的基本素养。第二个重试间隔用2 * (attempt 1)递增第一次失败等2秒第二次等4秒第三次等6秒避免立刻重试给对方造成压力。6.2 合规这块我建议你这样把握作为从业者我建议你在写每一只爬虫前都想清楚三件事。第一抓什么。只抓公开、非敏感的数据。涉及个人隐私、付费内容、需要登录才能看的资料不要碰。练习阶段用专门的教学站点或者自己搭建的站点练手最稳妥。第二怎么抓。先看目标网站的robots.txt比如http://quotes.toscrape.com/robots.txt里面写了哪些路径允许爬取。虽然robots.txt没有法律强制力但它代表站方的态度尊重它是爬虫行业的基本操守。抓取频率要控制在不影响对方服务正常运行的范围内通常每请求间隔1秒以上是安全线。第三抓来干嘛。学习研究、个人使用是正当场景。把数据用于商业用途、批量搬运他人内容就需要评估版权风险了。我见过不少因为爬虫吃官司的案例问题基本都出在“未经授权抓取非公开数据”和“抓取后商用”这两个环节。守住底线爬虫是技术工具用得好是效率神器用不好就是给自己挖坑。7. 把临时脚本变成小工具函数化重构与下一步路线前面几节的代码是能跑但如果让你抓100个页面、换个输出文件名、调整页数范围是不是还得改代码这就是脚本和工具的区别。一个合格的小工具应该能通过参数配置而不是改源码。这一节我们做一次完整重构。7.1 一次实战重构从30行到3个函数重构后的代码结构非常清晰职责分成了三块import csv import time import random import requests from bs4 import BeautifulSoup USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/119.0 Safari/537.36, ] BASE_URL http://quotes.toscrape.com/page/{}/ def fetch_page(url, retries3): headers {User-Agent: random.choice(USER_AGENTS)} for attempt in range(retries): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp.text except requests.RequestException as e: print(f第 {attempt 1} 次请求失败: {e}) time.sleep(2 * (attempt 1)) return None def parse_page(html): soup BeautifulSoup(html, html.parser) rows [] for quote in soup.select(div.quote): rows.append([ quote.select_one(span.text).get_text(), quote.select_one(small.author).get_text(), ,.join(tag.get_text() for tag in quote.select(a.tag)) ]) return rows def main(start1, end10, outputquotes.csv): all_rows [] for page in range(start, end 1): print(f正在抓取第 {page} 页...) html fetch_page(BASE_URL.format(page)) if html: all_rows.extend(parse_page(html)) time.sleep(1) with open(output, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([名言内容, 作者, 标签]) writer.writerows(all_rows) print(f完成共保存 {len(all_rows)} 条数据) if __name__ __main__: main(1, 5, my_quotes.csv)fetch_page负责网络请求包含User-Agent、超时、重试parse_page负责HTML解析输入HTML返回数据列表main负责流程编排控制页数范围、汇总数据和落盘。每个函数只管一件事哪出问题改哪清晰明了。if __name__ __main__:这行也值得多说一句。它的作用是只有直接运行这个脚本时才执行main()。如果这个文件被别人import不会自动跑爬虫。这是Python工程的标配写法从第一天写项目就应该保持。重构完你会发现想抓1到10页就改main(1, 10)想换个文件名就改第二个参数不用再动逻辑代码。这就是函数化的意义——把重复逻辑收拢把变化点暴露成参数。7.2 day15之后的进阶路线这篇做到这个程度你已经完成了“写一个爬虫—解析数据—落盘—封装成工具”的完整闭环。接下来往哪走我按推荐顺序给你一条路线第一结合数据分析。用pandas读CSV统计练习站里哪些标签出现频率最高用matplotlib画个柱状图。这一路正好衔接数据分析与可视化方向是性价比很高的下一步。第二学习session和cookie。找一个需要登录的练习站点理解会话维持的原理。这能帮你把“公开数据”这条线拓宽一点也为后面和接口打交道打基础。第三了解动态页面。用selenium或requests-html抓取一个JavaScript渲染的页面理解现代网页是怎么工作的以及为什么要尽可能去分析底层接口而不是用浏览器模拟。第四高强度爬取场景。当你觉得手写循环requests不够用了再去看Scrapy框架。Scrapy的并发调度、Item Pipeline、中间件机制都是为大规模爬取设计的。但我的建议是至少写过几十个requests爬虫再去学Scrapy否则一上来就被框架的大量概念淹没了。最后分享一个我自己的体会。很多人学爬虫总想着一次就把所有技巧吃透做页面采集就上分布式做登录就上图像识别结果每个都学得半吊子。我的经验是第一个爬虫越朴素越好。requests加BeautifulSoup这套组合你越熟练后面学什么框架都快。因为所有爬虫框架解决的本质上还是“请求—解析—存储”这三个环节的工程化问题。把今天这个十几行的小爬虫吃透你已经有了一只撬动整个数据领域的手。
返回列表