
简介这是一套面向编程零基础学习者的Python网络爬虫实战入门资源聚焦HTML解析驱动的数据采集与多格式存储全流程解决初学者从环境搭建、网页请求、结构化提取到本地持久化的完整链路问题。资源包共30个文件含17个可直接运行的Python脚本覆盖requests抓取、BeautifulSoup/lxml解析、维基百科词条递归采集、链接去重、数据库写入等典型场景、6份PDF技术文档含《Web Scraping with Python》核心章节、Beautiful Soup权威指南及Scrapy入门教程、5个XML配置与项目元数据文件以及README说明和IDE配置文件总大小96.04MB。内容预览显示项目已结构化组织为crawler-master主模块包含互联网采集、新闻抓取、图片正则提取、六度空间链接分析等进阶实践案例。已有39人下载学习配套代码即学即用注释清晰涵盖反爬应对思路、动态内容处理提示及数据合规采集原则是构建扎实爬虫工程能力的高实操性入门素材。1. 从零开始的爬虫之旅为什么是Python和HTML解析如果你是一个对数据感兴趣但又觉得编程门槛高不可攀的初学者那么“Python HTML解析”这个组合可能就是为你量身定做的第一把钥匙。我见过太多人一提到“爬虫”就觉得是黑客行为或者认为需要高深的计算机知识才能入门。其实不然爬虫的本质就是模拟一个普通用户去访问网页然后把网页上你感兴趣的信息用一种程序化的方式“拿”下来。这个过程和你用浏览器打开一个网页然后用眼睛看、用手复制粘贴到Excel里在逻辑上没有任何区别只是前者更快、更准、更不知疲倦。为什么是Python答案很简单它足够简单也足够强大。Python的语法接近自然英语一个完全没有编程基础的人花上半天时间就能看懂基础的变量、循环和判断语句。更重要的是围绕Python有一个极其繁荣的“生态”这意味着有无数前辈已经为你可能遇到的各种问题写好了现成的工具包库你只需要像搭积木一样调用它们。对于爬虫来说最核心的几块“积木”——requests用于网络请求BeautifulSoup或lxml用于解析HTML——都成熟得不能再成熟文档齐全社区活跃你遇到的99%的问题都能在网上找到答案。而HTML则是这一切的基石。你可以把整个互联网想象成一个由无数个HTML文档链接起来的巨大网络。每一个网页本质上就是一个HTML文件。你的浏览器收到这个文件后会按照HTML的规则把它渲染成你看到的图文并茂的页面。我们做爬虫就是直接去获取这个最原始的HTML文件然后从中提取我们需要的数据。所以学习爬虫第一步不是学多么复杂的编程技巧而是要学会看懂HTML的结构。这就像你要在一本书里找一段话你得先知道这本书的目录和章节是怎么排版的。HTML通过一系列的“标签”如div,p,a,span来定义内容的结构和样式我们的任务就是找到目标数据被哪个标签“包裹”着然后告诉程序把它“拆”出来。很多人卡在第一步觉得看HTML源码像看天书。其实你完全不需要成为前端开发专家。你只需要掌握最核心的一点HTML是一种嵌套的、有层次的结构。一个标签里面可以包含另一个标签形成父子关系。我们提取数据绝大多数时候就是在利用这种层次关系进行定位。比如你想抓取一个新闻列表你会发现每条新闻的标题都放在一个结构类似的div classnews-item里里面又分别有h2放标题span classtime放时间。你的爬虫程序要做的就是先找到所有class为news-item的div标签父级然后分别进入每一个这样的div去提取里面的h2和span的内容子级。理解了这一点你就掌握了基于HTML解析的爬虫最核心的思维模型。2. 环境搭建与核心工具库打造你的数据采集工具箱在开始写第一行爬虫代码之前我们需要先把“战场”布置好。这个过程可能会让一些新手感到畏惧但请放心跟着步骤一步步来绝对可以搞定。我个人的体会是一次性地把环境配置妥当远比每次运行时被各种报错折磨要高效得多。2.1 Python安装与虚拟环境管理首先你需要安装Python。访问Python官网下载最新稳定版本比如3.8以上的安装包。安装时务必勾选“Add Python to PATH”这个选项这能让你在系统的任何地方都能通过命令行调用Python。安装完成后打开命令行Windows上是CMD或PowerShellMac/Linux上是Terminal输入python --version如果能看到版本号恭喜你第一步成功了。接下来是至关重要的一步创建虚拟环境。这是很多教程会忽略但实际开发中必不可少的好习惯。虚拟环境相当于为你当前的项目创建一个独立的、干净的Python运行空间里面安装的库只对这个项目生效不会影响系统里其他的Python项目。这样可以完美解决不同项目依赖不同版本库的冲突问题。创建方法很简单在你打算存放项目的文件夹里打开命令行执行python -m venv spider_env这条命令会创建一个名为spider_env的文件夹里面就是独立的Python环境。然后你需要激活它Windows:spider_env\Scripts\activateMac/Linux:source spider_env/bin/activate激活后你的命令行提示符前面通常会显示(spider_env)表示你已经在这个虚拟环境里了。之后所有pip install的操作库都会被安装到这个环境里。2.2 爬虫核心三件套Requests, BeautifulSoup, lxml环境准备好后我们来安装爬虫最核心的三个库。在激活的虚拟环境下执行以下命令pip install requests beautifulsoup4 lxml我来解释一下为什么是这三个以及它们各自扮演什么角色Requests: 这是我们的“网络浏览器”。它的唯一任务就是向指定的网址URL发送HTTP请求并把服务器返回的响应通常是HTML文本拿回来。它比用浏览器手动访问更高效而且可以自定义请求头、携带参数、处理Cookie等。你可以把它想象成一个只会“要东西”和“收东西”的机器人。BeautifulSoup (bs4): 这是我们的“信息提取器”。Requests拿回来的是一大坨混杂着标签、样式、脚本的HTML字符串人类很难直接阅读。BeautifulSoup的作用就是解析这坨字符串把它变成一个结构清晰的、可以让我们方便地查询和遍历的“树状”对象。它提供了一系列像find(),find_all(),select()这样直观的方法让我们能根据标签名、属性如class,id来定位元素。它本身不负责解析需要指定一个“解析器”这就是为什么我们要安装lxml。lxml: 这是BeautifulSoup推荐使用的解析器速度非常快容错能力也强。虽然Python标准库里有html.parser但在处理复杂或不规范的HTML时lxml的表现要稳定得多。安装它相当于给BeautifulSoup配了一把锋利的刀。这里有一个非常重要的实操心得很多网站会对请求头进行简单的检查如果发现你不是一个正常的浏览器比如缺少User-Agent字段可能会拒绝你的请求或返回错误页面。因此在使用requests时养成习惯总是带上一个合理的请求头。一个最简单的例子import requests 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 } url https://example.com response requests.get(url, headersheaders)这个User-Agent字符串是模仿Chrome浏览器的能帮你绕过很多基础的反爬机制。记住requests.get()返回的response对象其.text属性就是我们需要的HTML字符串。3. 实战解析手把手拆解一个网页并提取数据理论说再多不如动手做一遍。我们找一个结构清晰、内容公开的网站作为练习目标。为了避免法律和伦理风险我们选择各大高校或机构提供的公开数据集页面或者像“豆瓣读书Top250”这类经典的、对爬虫相对友好的练习场实际操作时请注意频率避免对服务器造成压力。这里我们以解析一个简单的静态新闻列表页为例讲解完整的流程。假设我们有一个目标页面它的HTML结构大致如下这是简化后的核心部分html body div classnews-list div classnews-item h2a href/news/1Python 3.12 发布性能提升显著/a/h2 p classsummary最新版本的Python在解释器性能方面有重大优化.../p span classmeta发布于2023-10-02 | 来源官方/span /div div classnews-item h2a href/news/2人工智能辅助编程工具展望/a/h2 p classsummaryAI正在改变开发者编写代码的方式.../p span classmeta发布于2023-10-01 | 来源科技媒体/span /div /div /body /html我们的目标是提取每条新闻的标题、链接、摘要和发布日期。3.1 第一步获取页面与创建Soup对象首先我们用requests获取页面内容并用BeautifulSoup进行解析。import requests from bs4 import BeautifulSoup url 你的目标网址 headers {User-Agent: 你的浏览器UA} response requests.get(url, headersheaders) # 检查请求是否成功状态码200表示成功 if response.status_code 200: # 使用 lxml 解析器来解析 HTML 内容 soup BeautifulSoup(response.text, lxml) else: print(f请求失败状态码{response.status_code})这里有个关键细节一定要检查response.status_code。如果不是200可能是页面不存在、需要登录、触发了反爬等。直接对非200响应的.text进行操作可能会导致后续解析出错。3.2 第二步定位与提取数据现在soup对象就是整个页面的结构化代表。我们需要找到所有class为news-item的div标签。# 使用 find_all 方法找到所有符合条件的新闻条目 news_items soup.find_all(div, class_news-item) for item in news_items: # 在每个 news-item 内部进行提取接下来我们在每个item即一个div classnews-item内部进行精细提取。提取标题和链接标题在h2标签内的a标签里。title_tag item.find(h2).find(a) # 先找到h2再找到里面的a if title_tag: title title_tag.get_text(stripTrue) # 获取标签内的文本并去除首尾空格 link title_tag.get(href) # 获取a标签的href属性即链接 else: title, link None, None为什么用.get_text(stripTrue)因为标签内的文本可能包含换行和多余空格stripTrue参数能自动清理让数据更干净。提取摘要摘要在class为summary的p标签里。summary_tag item.find(p, class_summary) summary summary_tag.get_text(stripTrue) if summary_tag else None提取发布日期日期信息在span classmeta里但和其他信息混在一起。我们需要用字符串处理的方法把它分离出来。meta_tag item.find(span, class_meta) if meta_tag: meta_text meta_tag.get_text() # 假设格式固定可以用 split 方法分割字符串 # 例如“发布于2023-10-02 | 来源官方” parts meta_text.split(|) for part in parts: if 发布于 in part: publish_date part.replace(发布于, ).strip() break else: publish_date None else: publish_date None这个过程清晰地展示了爬虫数据提取的典型模式先定位大容器父节点再逐层深入定位具体的子元素最后提取文本或属性。find()和find_all()是最常用的方法find()返回第一个匹配的元素find_all()返回一个列表。3.3 第三步应对HTML结构变化与异常处理网页结构不是一成不变的网站改版是常有的事。一个健壮的爬虫必须考虑这种可能性。上面的代码有一个潜在问题如果某个news-item里缺少h2或p class”summary”直接调用.find()再链式调用会抛出AttributeError因为None对象没有.find()方法。更安全的写法是每一步都做判断或者使用try...except。此外BeautifulSoup的select()方法支持CSS选择器有时写起来更简洁直观。例如提取所有标题链接可以写成title_links soup.select(div.news-item h2 a) for link in title_links: print(link.get_text(), link[href])select(div.news-item h2 a)的意思就是选择所有在div class”news-item”内部的h2内部的a标签。这种写法在复杂嵌套结构中尤其好用。一个重要的经验在编写提取逻辑时不要一次性写死。应该先用浏览器开发者工具F12仔细分析目标页面的HTML结构把可能的变化点比如某个类名、某个包裹层级考虑进去。写好代码后先用一小部分数据测试打印出中间结果确保每一步都按预期工作再处理大量数据。4. 数据的归宿从内存到硬盘的存储策略费了九牛二虎之力把数据从网上“扒”下来如果只是打印在屏幕上或者留在程序的内存里程序一关就全没了这显然不是我们想要的结果。数据存储是爬虫项目的“最后一公里”也是体现项目价值的关键。根据数据量、后续用途和个人习惯有几种常见的存储方式。4.1 CSV文件轻量级结构化存储的首选对于大多数初学者和小规模项目CSV逗号分隔值文件是最友好、最通用的选择。它可以用Excel、Numbers或文本编辑器直接打开查看也可以轻松导入到数据库或其他分析工具中。Python标准库中的csv模块让读写CSV变得非常简单。接续上面的例子我们将提取到的数据存储到CSV文件中import csv # 假设我们已经有了一个包含多条新闻数据的列表 news_data # 每条数据是一个字典例如{title: ..., link: ..., summary: ..., date: ...} filename news_data.csv # 定义CSV文件的列名表头 fieldnames [title, link, summary, publish_date] with open(filename, w, newline, encodingutf-8-sig) as csvfile: writer csv.DictWriter(csvfile, fieldnamesfieldnames) writer.writeheader() # 写入表头 for news in news_data: writer.writerow(news) # 逐行写入数据这里有三个关键细节newline这个参数在Windows系统下尤为重要可以避免写入的行之间出现空行。encodingutf-8-sig使用UTF-8编码并写入BOM签名。这对于包含中文等非ASCII字符的数据非常友好能确保用Excel打开时不会出现乱码。如果确定只用其他工具处理使用utf-8也可以。DictWriter它允许我们使用字典来写入数据字典的键对应CSV的列名这样代码可读性更高也不容易搞错列的顺序。4.2 JSON文件嵌套结构数据的天然载体如果采集的数据本身结构就比较复杂比如每条数据里又包含一个列表或字典例如一条商品信息里包含多个颜色尺码的库存那么JSON格式比CSV更合适。JSON保持了数据的层次结构并且被几乎所有编程语言支持。Python的json模块使用起来更是直观。import json filename news_data.json # 直接将整个 news_data 列表列表内是字典保存为JSON文件 with open(filename, w, encodingutf-8) as f: # indent 参数让JSON文件格式化输出更易于阅读 json.dump(news_data, f, ensure_asciiFalse, indent2)ensure_asciiFalse这个参数至关重要它保证中文字符能以其原本的形式而不是\uXXXX的Unicode转义序列存储到文件中方便直接阅读。4.3 数据库入门SQLite的便捷之道当数据量变大或者你需要频繁地查询、更新数据时文件存储就显得力不从心了。这时就该数据库登场了。对于个人项目或中小型爬虫SQLite是一个完美的起点。它不需要安装独立的数据库服务器整个数据库就是一个文件用Python标准库sqlite3就能直接操作功能却一点也不弱。首先我们需要设计一张表来存储新闻数据import sqlite3 # 连接到数据库文件如果不存在则会自动创建 conn sqlite3.connect(spider_data.db) cursor conn.cursor() # 创建表 create_table_sql CREATE TABLE IF NOT EXISTS news ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, link TEXT UNIQUE, -- 链接设为唯一避免重复存储 summary TEXT, publish_date TEXT, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 记录爬取时间 ) cursor.execute(create_table_sql) # 插入数据假设 news 是一个字典 insert_sql INSERT OR IGNORE INTO news (title, link, summary, publish_date) VALUES (?, ?, ?, ?) data_tuple (news[title], news[link], news[summary], news[publish_date]) cursor.execute(insert_sql, data_tuple) # 提交事务使更改生效 conn.commit() # 关闭连接 cursor.close() conn.close()这里有几个必须注意的要点使用参数化查询?占位符绝对不要用字符串拼接的方式将变量直接放入SQL语句如f”INSERT … VALUES (‘{title}’)”这会导致严重的SQL注入安全风险。使用?占位符和元组传参是既安全又规范的做法。INSERT OR IGNORE这是一个非常实用的技巧。我们为link字段设置了UNIQUE约束当尝试插入一条链接已存在的记录时OR IGNORE会忽略这次插入避免数据重复。对于爬虫来说这能有效避免因重复爬取相同页面导致的数据冗余。事务提交对数据库的增删改操作需要在最后执行conn.commit()才会真正保存。养成好习惯在完成一批操作后统一提交效率更高。关闭连接操作完成后显式地关闭cursor和connection是一个好习惯虽然在某些情况下解释器退出时会自动关闭但显式关闭能确保资源立即释放。对于初学者从CSV开始过渡到JSON处理复杂数据再在项目需要时引入SQLite是一个平滑且实用的学习路径。存储方式的选择没有绝对的好坏只有是否适合当前的项目阶段和需求。5. 避坑指南与效率提升新手常犯的五个错误掌握了基本流程后想要让爬虫跑得更稳、更快、更长久就需要了解一些常见的“坑”和优化技巧。这些都是我在早期爬虫实践中用时间和报错信息换来的经验。5.1 错误一忽视请求头与基础反爬这是新手最常踩的坑。你的代码在本机运行得好好的一放到服务器上或者多跑几次就返回403错误或者跳转到验证页面。很大概率是因为你的请求头太“假”了。除了之前提到的User-Agent一个看起来更真实的请求头还应该包含Referer你从哪个页面跳转过来的、Accept-Language等字段。你可以用浏览器开发者工具的“网络(Network)”选项卡查看一次真实的网页请求所携带的所有头部信息然后模仿着构建你的headers字典。进阶技巧有些网站会检查Cookie来维持会话状态。对于这类网站你可以先用浏览器手动登录然后从开发者工具中复制出Cookie字符串添加到你的requests请求头中。但请注意Cookie通常有有效期且涉及隐私此方法仅用于学习测试。5.2 错误二缺乏异常处理与重试机制网络是不稳定的目标服务器也可能临时出问题。如果你的代码里没有try...except那么一次网络波动就可能导致整个程序崩溃。对于网络请求最基本的异常处理是必要的。import time from requests.exceptions import RequestException def safe_request(url, headers, retries3): for i in range(retries): try: resp requests.get(url, headersheaders, timeout10) # 设置超时 resp.raise_for_status() # 如果状态码不是200抛出HTTPError异常 return resp except RequestException as e: print(f第{i1}次请求失败: {e}) if i retries - 1: wait_time 2 ** i # 指数退避等待1, 2, 4秒... print(f等待{wait_time}秒后重试...) time.sleep(wait_time) else: print(重试次数用尽放弃请求。) return None这个safe_request函数实现了简单的重试机制和指数退避等待能显著提升程序在恶劣网络环境下的健壮性。timeout参数也至关重要它防止程序因为某个请求没有响应而永远卡住。5.3 错误三暴力请求触发反爬被封IP如果你编写一个循环一秒钟内请求同一个网站几十次服务器很可能会把你的IP地址暂时甚至永久封禁。这是最直接的反爬手段。尊重网站控制频率是爬虫工程师的基本素养。最朴素也最有效的方法是在每次请求之间随机休眠一段时间。import time import random for url in url_list: response requests.get(url, headersheaders) # ... 处理数据 ... # 随机等待1到3秒 time.sleep(random.uniform(1, 3))对于严肃的项目可以考虑使用更智能的调度或者通过代理IP池来分散请求。但对于初学者和道德爬取time.sleep()就是你的好朋友。5.4 错误四解析逻辑过于脆弱依赖固定结构你的解析代码严重依赖于div class”news-item”这样的特定标签和类名。一旦网站前端改版类名变了或者HTML结构微调了你的爬虫立刻失效。为了增加鲁棒性多用相对定位少用绝对定位不要依赖像soup.find_all(‘div’)[7]这样的绝对索引。组合使用多种属性如果一个class不稳定可以尝试结合标签名、其他属性如id、>response.encoding gbk # 或者 ‘utf-8’ ‘gb2312’ html_text response.text更稳妥的方法是使用response.content它是原始的字节流然后用chardet这样的库去检测编码import chardet raw_data response.content detected_encoding chardet.detect(raw_data)[encoding] html_text raw_data.decode(detected_encoding, errorsignore) # errors参数决定如何处理解码错误处理编码问题虽然繁琐但却是保证数据质量不可跳过的一环。看到乱码不要慌检查并修正编码设置十有八九能解决问题。6. 项目实战构建一个完整的、可复用的爬虫脚本现在我们把前面所有的知识点串联起来构建一个结构清晰、功能完整、具有一定容错能力的爬虫脚本。这个脚本将包含配置、请求、解析、存储和日志等模块虽然简单但骨架是专业的。6.1 项目结构设计一个好的项目应该易于理解和维护。我们为这个爬虫项目设计一个简单的结构my_spider_project/ ├── config.py # 配置文件存放URL、请求头、数据库路径等 ├── spider.py # 主爬虫逻辑 ├── storage.py # 数据存储相关函数CSV、数据库 ├── utils.py # 工具函数如安全请求、日志设置 └── main.py # 程序入口调度整个流程config.py集中管理配置方便修改。# config.py BASE_URL https://example-news-site.com/list HEADERS { User-Agent: Mozilla/5.0 ..., Accept-Language: zh-CN,zh;q0.9, } DB_PATH data/news.db CSV_PATH data/news.csv REQUEST_TIMEOUT 10 RETRY_TIMES 3utils.py封装工具函数。# utils.py import time import random import logging from requests.exceptions import RequestException import requests def setup_logger(): 配置日志方便追踪程序运行状态 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(spider.log), logging.StreamHandler() ] ) return logging.getLogger(__name__) def safe_request(url, headers, timeout, retries): 带重试和异常处理的请求函数 logger logging.getLogger(__name__) for i in range(retries): try: resp requests.get(url, headersheaders, timeouttimeout) resp.raise_for_status() # 随机延迟模拟人类操作避免请求过快 time.sleep(random.uniform(1, 2)) return resp except RequestException as e: logger.warning(f请求 {url} 失败 (尝试 {i1}/{retries}): {e}) if i retries - 1: wait (2 ** i) random.random() time.sleep(wait) else: logger.error(f请求 {url} 最终失败。) return None6.2 主爬虫逻辑 (spider.py)这是核心负责抓取和解析。# spider.py from bs4 import BeautifulSoup from config import BASE_URL, HEADERS, REQUEST_TIMEOUT, RETRY_TIMES from utils import safe_request, setup_logger logger setup_logger() def fetch_page(page_num): 获取指定页码的列表页 url f{BASE_URL}?page{page_num} logger.info(f开始抓取: {url}) response safe_request(url, HEADERS, REQUEST_TIMEOUT, RETRY_TIMES) if response and response.status_code 200: return response.text else: return None def parse_list_page(html): 解析列表页提取详情页链接和基础信息 soup BeautifulSoup(html, lxml) news_list [] # 假设列表项容器是 article.news-card items soup.select(article.news-card) for item in items: try: title_elem item.select_one(h2 a) title title_elem.get_text(stripTrue) if title_elem else N/A link title_elem[href] if title_elem else # # 补全可能为相对路径的链接 if link.startswith(/): link https://example-news-site.com link summary_elem item.select_one(p.description) summary summary_elem.get_text(stripTrue) if summary_elem else date_elem item.select_one(time) publish_date date_elem[datetime] if date_elem and date_elem.has_attr(datetime) else (date_elem.get_text(stripTrue) if date_elem else ) news_list.append({ title: title, link: link, summary: summary, publish_date: publish_date }) except Exception as e: logger.error(f解析列表项时出错: {e}, exc_infoTrue) continue # 跳过此项继续解析下一个 logger.info(f本页解析到 {len(news_list)} 条新闻。) return news_list6.3 数据存储 (storage.py)提供不同的存储方式。# storage.py import csv import json import sqlite3 from config import DB_PATH, CSV_PATH def save_to_csv(data_list, filenameCSV_PATH): 保存数据到CSV文件 if not data_list: return fieldnames data_list[0].keys() with open(filename, a, newline, encodingutf-8-sig) as f: # 使用追加模式 ‘a’ writer csv.DictWriter(f, fieldnamesfieldnames) if f.tell() 0: # 如果文件是空的写入表头 writer.writeheader() writer.writerows(data_list) def save_to_db(data_list, db_pathDB_PATH): 保存数据到SQLite数据库 conn sqlite3.connect(db_path) cursor conn.cursor() # 确保表存在同前文 create_table_sql CREATE TABLE IF NOT EXISTS news (...) cursor.execute(create_table_sql) insert_sql INSERT OR IGNORE INTO news (title, link, summary, publish_date) VALUES (?, ?, ?, ?) for data in data_list: cursor.execute(insert_sql, (data[title], data[link], data[summary], data[publish_date])) conn.commit() conn.close()6.4 程序入口 (main.py)负责调度整个流程比如爬取前5页。# main.py from spider import fetch_page, parse_list_page from storage import save_to_csv, save_to_db from utils import setup_logger logger setup_logger() def main(): all_news [] for page in range(1, 6): # 爬取1到5页 html fetch_page(page) if html: news_on_page parse_list_page(html) all_news.extend(news_on_page) else: logger.error(f第 {page} 页抓取失败停止后续抓取。) break if all_news: logger.info(f共抓取到 {len(all_news)} 条数据开始存储。) # 可以选择存储到CSV或数据库或两者都存 save_to_csv(all_news) save_to_db(all_news) logger.info(数据存储完毕。) else: logger.warning(未抓取到任何数据。) if __name__ __main__: main()这个项目结构虽然基础但已经具备了模块化、配置化、日志化和错误处理等良好实践的雏形。你可以在此基础上轻松地扩展功能比如增加代理支持、解析详情页、定时任务调度等。记住爬虫项目不是一蹴而就的而是需要根据目标网站的变化和自身需求的增长不断迭代和维护的。从这样一个清晰的结构开始会让未来的维护工作轻松很多。本文还有配套的精品资源点击获取