Python爬虫实战:用requests与BeautifulSoup抓取Instructables项目数据

发布时间:2026/5/30 16:04:06

Python爬虫实战:用requests与BeautifulSoup抓取Instructables项目数据 1. 项目概述与核心价值如果你是一个喜欢在Instructables这类DIY社区分享作品的创客或者是一个关注特定领域项目流行度的研究者你可能会好奇某个作者最受欢迎的项目是什么哪些类型的教程更容易获得关注项目发布后的热度变化趋势如何手动一个个点开项目页面去记录浏览量不仅效率低下而且难以进行批量分析和长期追踪。这正是网页爬虫技术可以大显身手的地方。简单来说网页爬虫就像是一个不知疲倦的、高度定制化的数据收集员。它能够按照我们设定的规则自动访问网页从看似复杂的HTML代码中精准地“挖出”我们需要的信息比如标题、链接、浏览量、发布时间等并将它们整理成结构化的数据。今天我们就以Python为工具深入实战一步步构建一个能够自动抓取Instructables用户所有项目浏览量的爬虫脚本。这个实战项目的核心价值在于“自动化”和“数据驱动”。通过它你可以个人项目分析量化自己作品的受欢迎程度分析哪种题材或风格更受社区青睐为未来的创作方向提供数据参考。竞品或领域研究追踪特定领域如3D打印、电子电路内活跃创客的项目数据了解热门趋势和技术焦点。社区动态监控定期抓取数据观察项目热度的增长曲线甚至结合发布时间分析“爆款”的传播规律。我们将使用Python生态中两个极其经典和强大的库requests用于模拟浏览器发起网络请求获取网页原始代码BeautifulSoup用于解析HTML像使用导航地图一样在复杂的网页结构中轻松定位目标数据。整个过程清晰、直接即使你是Python新手跟着做下来也能获得一个立刻可用的工具。2. 环境准备与工具选型解析工欲善其事必先利其器。在开始写代码之前我们需要搭建好开发环境。这里的选择背后都有其考量并非随意指定。2.1 Python环境与IDE选择首先你需要一个Python环境。我强烈建议使用Python 3.7及以上版本因为它们对现代库的支持更好并且包含了我们所需的所有基础功能。你可以从Python官网下载安装包。安装时请务必勾选“Add Python to PATH”选项这能让你在命令行中直接使用python和pip命令省去后续配置环境变量的麻烦。关于集成开发环境IDE原项目提到了PyCharm它确实是一款功能强大的专业IDE尤其适合大型项目。但对于我们这样的脚本级任务完全有更轻量、更快速的选择。我个人更推荐使用Visual Studio Code (VS Code)搭配Python插件或者Jupyter Notebook。VS Code启动快插件生态丰富对Markdown、JSON等文件支持极佳调试功能也很方便。特别适合这种“写一个脚本跑一下看看结果”的场景。Jupyter Notebook以单元格Cell为单位运行代码非常适合数据探索和阶段性测试。你可以分步执行请求、解析、数据提取实时看到每一步的输出对于学习和调试爬虫逻辑非常直观。当然如果你已经熟悉并喜欢PyCharm继续使用它完全没有问题。核心在于选择一个让你写代码舒服的工具。2.2 核心库requests与BeautifulSoup我们的脚本主要依赖两个第三方库需要通过pip命令安装。打开你的命令行终端Windows上是CMD或PowerShellmacOS/Linux上是Terminal执行以下命令pip install requests beautifulsoup4这里解释一下为什么是这两个库以及安装beautifulsoup4而不是beautifulsouprequests库它是Python中用于发送HTTP请求的“事实标准”。相比于Python内置的urllib模块requests的API设计更加人性化代码更简洁。例如发送一个GET请求只需要requests.get(url)一行代码而urllib则需要好几行。它自动处理连接池、会话保持、SSL验证等底层细节让我们能专注于业务逻辑。beautifulsoup4库这是BeautifulSoup库的第四个主要版本也是当前维护和使用的版本。它的包名就是beautifulsoup4但导入时我们使用from bs4 import BeautifulSoup。这个库是HTML/XML解析器它能够将复杂的HTML文档转换成一个复杂的树形结构解析树每个节点都是Python对象。我们可以通过标签名、CSS类、ID等属性像在数据库中查询一样轻松地找到想要的元素。它支持多种解析器后端如lxml,html.parser我们这里使用Python标准库自带的html.parser无需额外安装虽然速度不是最快但绝对够用且稳定。注意在安装库时如果遇到速度慢或超时的问题可以临时使用国内的镜像源例如清华源pip install requests beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple。3. 网页结构分析与爬虫逻辑设计写爬虫代码一半是编程另一半是“侦探工作”——仔细查看目标网页的HTML结构。我们不能盲目地写代码必须先搞清楚数据藏在哪里以及如何定位它。3.1 目标页面结构探查我们的目标是抓取一个Instructables用户例如FuzzyPotato的所有项目及其浏览量。这需要分两步走进入用户的项目列表页获取所有项目的标题和链接。逐个访问每个项目的详情页提取浏览量数据。首先用浏览器打开一个用户的项目列表页比如https://www.instructables.com/member/FuzzyPotato/instructables/。然后按下F12键打开开发者工具切换到“元素”Elements或“检查器”Inspector标签页。你会发现每个项目都被包裹在一个div容器里。通过仔细观察和点击不同的项目块你需要找到这个容器独一无二的标识。通常网站会使用CSS类class来定义样式这正好成为我们定位的“锚点”。经过探查我们发现每个项目卡片都有一个类名为thumbnail ible-thumbnail的div标签。这就是我们抓取项目列表的关键选择器。接着在这个项目卡片div内部我们需要找到项目标题和其详情页的相对链接。通常标题会是一个带有a超链接标签的文字并且这个链接的href属性指向项目的详情页。通过检查我们发现标题链接的CSS类是title。所以在一个项目卡片内我们可以用instructable.find(“a”, class_“title”)来找到这个链接元素通过.text属性获取标题文本通过[“href”]属性获取链接地址。3.2 详情页数据定位策略获取到项目详情页的URL后我们需要访问它并找到浏览量数据。同样使用开发者工具在项目详情页找到显示浏览量的区域。检查元素后我们发现浏览量数据通常在一个p标签里并且带有一个特定的类比如svg-views view-count。这个标签内的文本如 “12,345 views”就是我们需要的数字。这里有一个非常重要的细节网页上显示的“12,345”是带逗号的字符串而我们需要的是整数12345以便后续计算。因此在提取文本后我们需要用Python的字符串方法.replace(‘,’, ”)去掉逗号再用int()转换为整数。3.3 爬虫流程与异常处理设计基于以上分析我们的爬虫核心逻辑流程图如下文字描述输入目标用户的Instructables用户名。构建列表页URL拼接成https://www.instructables.com/member/{username}/instructables/。请求与解析列表页使用requests.get获取页面用BeautifulSoup解析查找所有class”thumbnail ible-thumbnail”的div元素得到项目卡片列表。遍历项目卡片对每个卡片提取标题和相对URL并拼接成完整的详情页绝对URL。请求与解析详情页对每个详情页URL再次使用requests.get和BeautifulSoup查找class”svg-views view-count”的p元素提取并清洗浏览量文本。数据存储与输出将(标题, URL, 浏览量)这个三元组保存起来例如存入列表或字典并打印或保存到文件。异常处理网络请求可能失败如404、503页面结构可能微调导致找不到元素。我们的代码必须包含try-except块来优雅地处理这些情况避免因一个项目出错导致整个脚本崩溃。这个设计确保了爬虫的鲁棒性和可扩展性。理解了这些再看代码就会觉得每一行都理所当然了。4. 核心代码实现与逐行解读现在让我们把设计思路转化为实际的Python代码。我将提供一个增强版的脚本它包含了错误处理、进度提示等更实用的功能。import requests from bs4 import BeautifulSoup import time def scrape_instructables_views(username): 抓取指定Instructables用户所有项目的浏览量。 参数: username (str): Instructables网站的用户名 返回: list: 包含字典的列表每个字典有title, url, views三个键 # 1. 构建用户项目列表页URL base_url https://www.instructables.com list_page_url f{base_url}/member/{username}/instructables/ print(f开始抓取用户 {username} 的项目...) print(f列表页: {list_page_url}) # 2. 请求项目列表页 try: list_response requests.get(list_page_url, timeout10) list_response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 except requests.exceptions.RequestException as e: print(f请求列表页失败: {e}) return [] list_soup BeautifulSoup(list_response.content, html.parser) # 3. 查找所有项目卡片 project_cards list_soup.find_all(div, class_thumbnail ible-thumbnail) if not project_cards: print(未找到任何项目卡片。请检查用户名是否正确或网站结构是否已更改。) return [] total_projects len(project_cards) print(f找到 {total_projects} 个项目。开始抓取详情页...) projects_data [] # 4. 遍历每个项目卡片 for index, card in enumerate(project_cards, 1): try: # 4.1 提取标题和相对链接 title_link card.find(a, class_title) if not title_link: print(f 项目 {index}/{total_projects}: 未找到标题链接跳过。) continue project_title title_link.text.strip() relative_url title_link.get(href) if not relative_url: print(f 项目 {project_title}: 未找到链接跳过。) continue project_url base_url relative_url # 4.2 请求项目详情页 # 添加延迟避免请求过快对服务器造成压力或被封IP time.sleep(1) # 延迟1秒 print(f 正在处理 ({index}/{total_projects}): {project_title[:50]}...) detail_response requests.get(project_url, timeout10) detail_response.raise_for_status() detail_soup BeautifulSoup(detail_response.content, html.parser) # 4.3 提取浏览量 # 注意类名可能包含空格find方法可以正确处理 view_count_element detail_soup.find(p, class_svg-views view-count) if view_count_element: # 提取文本移除“views”字样和逗号转换为整数 views_text view_count_element.text.strip() # 处理格式如 “1,234 views” 或 “1234 views” views_num int(views_text.lower().replace(views, ).replace(,, ).strip()) else: # 如果找不到浏览量元素标记为None views_num None print(f 警告: 未找到浏览量数据。) # 4.4 保存数据 project_info { title: project_title, url: project_url, views: views_num } projects_data.append(project_info) print(f 标题: {project_title}) print(f 浏览量: {views_num if views_num is not None else N/A}) print(f ---) except requests.exceptions.RequestException as e: print(f 处理项目 {project_title} 时请求出错: {e}) continue except ValueError as e: print(f 处理项目 {project_title} 的浏览量转换出错: {e} (原始文本: {views_text})) continue except Exception as e: print(f 处理项目 {project_title} 时发生未知错误: {e}) continue # 5. 汇总输出 print(f\n抓取完成共成功处理 {len(projects_data)}/{total_projects} 个项目。) return projects_data # 6. 主程序入口 if __name__ __main__: # 替换为你想要抓取的用户名 target_username FuzzyPotato results scrape_instructables_views(target_username) # 简单打印结果摘要 if results: print(\n 项目浏览量统计摘要 ) # 按浏览量降序排序忽略None值 sorted_results sorted([p for p in results if p[views] is not None], keylambda x: x[views], reverseTrue) for i, proj in enumerate(sorted_results[:10], 1): # 显示前10个 print(f{i:2d}. {proj[views]:8,} 浏览 - {proj[title][:60]}...) # 保存到CSV文件以便进一步分析 import csv filename f{target_username}_projects_views.csv with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, url, views]) writer.writeheader() writer.writerows(results) print(f\n详细数据已保存到文件: {filename})4.1 代码关键点解析函数封装我们将核心逻辑封装在scrape_instructables_views(username)函数中。这提高了代码的模块化和可重用性。你只需要调用这个函数并传入用户名就能得到数据。健壮的错误处理requests.get()用try-except包裹捕获网络超时、连接错误、HTTP错误如404页面不存在等。response.raise_for_status()会在HTTP状态码为4xx或5xx时主动抛出异常让我们能及时知晓请求失败。在遍历项目时对每个项目的处理也单独用try-except包裹。这样即使某个项目页面结构异常、链接失效也不会影响其他项目的抓取。捕获ValueError来处理浏览量文本转换为整数时可能出现的错误例如文本格式意外变化。请求延迟 (time.sleep(1)): 这是一个非常重要的网络礼仪。不加延迟地快速连续发送大量请求会对目标网站服务器造成压力可能被视为恶意攻击导致你的IP地址被暂时或永久封禁。添加1秒的间隔是一个简单有效的友好措施。对于大量数据抓取可能需要更复杂的速率限制策略。数据清洗views_text.lower().replace(‘views’, ”).replace(‘,’, ”).strip()这行代码做了多步清洗转小写、移除“views”单词、移除逗号千位分隔符、去除首尾空格最后得到纯净的数字字符串以供转换。数据持久化脚本最后将数据保存为CSV文件。CSV格式可以被Excel、Numbers、Python的pandas库等广泛支持便于后续分析和可视化。5. 常见问题排查与实战技巧在实际操作中你几乎一定会遇到一些问题。下面是我在多次爬虫实践中总结的常见“坑”及其解决方案。5.1 请求被拒绝或返回异常内容问题现象requests.get()返回的状态码是403禁止访问或者返回的HTML内容不是预期的页面而是包含“Access Denied”、“Bot detected”等字样的验证页面。原因分析网站启动了反爬虫机制。常见的措施包括检查请求头特别是User-Agent、检测请求频率、验证Cookie或会话。解决方案设置请求头模拟真实浏览器的请求。最基本的需要添加User-Agent。headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 } response requests.get(url, headersheaders, timeout10)使用会话requests.Session()可以自动保持Cookie模拟一个完整的浏览器会话对于需要登录或有多步交互的网站更有效。session requests.Session() session.headers.update(headers) response session.get(url)进一步伪装可以添加Accept、Accept-Language、Referer等常见的请求头字段使其更像普通用户访问。5.2 找不到HTML元素返回空列表或None问题现象find_all或find返回空列表或None即使你在浏览器里明明能看到那个元素。原因分析动态加载网页内容是通过JavaScript在浏览器端动态渲染的。requests获取的是初始的HTML源码不包含JS执行后添加的内容。你需要的数据可能不在其中。类名/结构变化网站改版了HTML结构或CSS类名已经更新。解析器差异极少数情况下html.parser的解析结果可能与浏览器略有差异。解决方案确认数据来源在开发者工具中查看“网络”(Network)标签页过滤XHR或Fetch请求看是否有直接返回数据的API接口。如果能找到直接请求这个API接口获取JSON数据比解析HTML更简单、更稳定。这是首选方案。检查类名右键点击元素选择“复制” - “复制选择器”或“复制完整XPath”看看类名是否包含动态生成的部分如js-前缀或随机字符串。使用更宽松的选择器如果不确定完整的类名可以使用CSS选择器部分匹配。# 查找class属性包含‘thumbnail’的div标签 project_cards list_soup.find_all(div, class_lambda c: c and thumbnail in c)应对动态内容如果确认是JS动态加载则需要使用Selenium或Playwright这类能控制真实浏览器的工具来获取渲染后的页面。但这会复杂很多。5.3 数据提取或转换错误问题现象在将浏览量文本转换为整数时程序抛出ValueError。原因分析提取到的文本格式与预期不符。例如可能变成了“1.2k views”1.2千、“No views yet”尚无浏览或者包含了意想不到的字符。解决方案增强数据清洗逻辑的鲁棒性。def parse_view_count(text): text text.strip().lower() if no views in text or 尚未 in text: # 处理无浏览量的情况 return 0 # 移除所有非数字和点、k、K的字符 import re num_text re.sub(r[^\dk.k], , text) # 保留数字、点、k/K if k in num_text: # 处理千位表示如 “1.2k” - 1200 num float(num_text.replace(k, )) return int(num * 1000) try: return int(num_text) except ValueError: # 如果还是无法转换记录日志并返回None print(f无法解析的浏览量文本: ‘{text}‘) return None5.4 性能优化与扩展建议当你要抓取大量用户或长期监控时基础脚本可能需要优化。并发请求使用concurrent.futures.ThreadPoolExecutor或asyncioaiohttp库可以同时发起多个详情页请求极大缩短总耗时。但务必注意控制并发数并添加更严格的延迟避免对服务器造成冲击。数据去重与增量抓取将已抓取的项目URL或ID保存到数据库或文件下次运行时只抓取新项目。使用数据库对于大规模数据将结果存入SQLite、MySQL或MongoDB比CSV文件更利于查询和管理。添加日志系统使用Python内置的logging模块替代print可以更方便地控制输出级别DEBUG, INFO, WARNING, ERROR并将日志记录到文件便于后期排查问题。遵守robots.txt在抓取前访问https://www.instructables.com/robots.txt查看网站是否允许爬虫抓取相关路径。这是尊重网站规则的体现。网页爬虫是一个需要细心、耐心和不断调试的过程。最宝贵的经验往往来自于解决一个又一个具体的错误。当你成功运行脚本看到数据一行行出现在屏幕上时那种成就感就是驱动你学习更多技术的动力。希望这个详细的实战指南能帮你顺利起步并为你打开数据采集与分析的大门。

相关新闻