- 示例工程
【免费下载链接】python-mini-projects
A collection of simple python mini projects to enhance your python skills
导读
本文围绕 python-mini-projects 仓库中的「Scraping Medium Articles」小项目展开,讲解如何用 Python 写一个命令行脚本:用户输入一篇 Medium 文章的 URL,脚本自动抓取该页面 HTML,清洗掉标签后提取标题、作者、导语与正文,并保存为纯文本文件到本地scraped_articles目录。读完本文,你将掌握 requests 抓取网页、BeautifulSoup 解析 DOM、正则清洗文本以及文件自动落盘这一套完整的静态网页抓取流程,并能直接在本仓库目录中运行验证。
项目定位:一条命令把 Medium 文章变成本地 TXT
Medium 是全球程序员常用的技术写作平台,页面结构稳定、正文语义清晰,非常适合作为网页抓取的入门练习对象。该项目的核心目标非常聚焦:给定一篇 Medium 文章 URL,抓取其纯文本内容并落盘保存。项目入口文档位于 projects/Scraping Medium Articles/README.md,核心实现只有一个文件 scraping_medium.py,依赖仅两个第三方库,非常适合初学者读懂、改跑、再扩展。
项目目录结构如下:
projects/Scraping Medium Articles/ ├── README.md # 项目说明 ├── requirements.txt # 第三方依赖清单 ├── scraping_medium.py # 核心抓取脚本 └── scraped_articles/ # 抓取结果输出目录(已含 3 篇示例) ├── One_month_into_the_MLH_Fellowship.txt ├── One_stop_guide_to_Google_Summer_of_Code.txt └── The_Pros_and_Cons_of_Open_Source_Software.txtscraped_articles目录中已经预置了 3 个抓取结果示例文件,用于直观展示脚本的输出格式,这也是验证脚本是否正常工作的现成参考。
环境准备与依赖安装
根据 README.md 的 Prerequisites 一节,运行前需要满足两个条件:
- 安装依赖:使用
pip安装 requirements.txt 中列出的模块; - 网络连通:设备上需要有可用的网络连接,因为脚本需要实时访问 Medium 页面。
requirements.txt 中固定了两个核心依赖及其版本:
beautifulsoup4==4.9.1 requests==2.23.0安装命令:
pip install -r "projects/Scraping Medium Articles/requirements.txt"说明:上述版本号是仓库中锁定的版本,若你本机 Python 环境较新,
pip在安装时可能会自动升级到满足该版本约束的最新兼容版本;若遇到依赖冲突,可去掉版本号直接pip install requests beautifulsoup4。
运行方式:像运行普通 Python 文件一样
README 明确指出,运行该脚本与运行任意 Python 文件无异:
python "projects/Scraping Medium Articles/scraping_medium.py"启动后脚本会提示:
Enter url of a medium article:粘贴一篇 Medium 文章链接并回车,脚本随即完成抓取、清洗与保存,并在终端打印保存位置,例如:
File saved in directory ./scraped_articles/One_month_into_the_MLH_Fellowship.txt整个交互只有一次输入,属于典型的「一次性输入、自动处理」的命令行工具。
源码深度解析:四步完成一篇文章的抓取
虽然脚本只有 70 余行,却完整覆盖了网页抓取的四个核心环节。下面按 scraping_medium.py 的代码顺序逐段剖析。
1. 定位脚本目录,保证输出路径稳定
脚本开头有一行容易被忽略的关键代码(scraping_medium.py):
# switching to current running python files directory os.chdir('\\'.join(__file__.split('/')[:-1]))它把当前工作目录切换到脚本自身所在目录,从而确保后续创建的./scraped_articles输出目录始终位于脚本旁边,而不是用户运行命令时所在的随机目录。这样做的好处是:无论从哪个路径调用脚本,输出位置都是确定且可预期的。
2.get_page():URL 校验 + 页面请求与解析
get_page()函数(scraping_medium.py)负责三个阶段的工作:
def get_page(): global url url = input('Enter url of a medium article: ') # handling possible error if not re.match(r'https?://medium.com/',url): print('Please enter a valid website, or make sure it is a medium article') sys.exit(1) res = requests.get(url) res.raise_for_status() soup = BeautifulSoup(res.text, 'html.parser') return soup- URL 合法性校验:用正则
r'https?://medium.com/'检查用户输入,只接受以http://medium.com/或https://medium.com/开头的地址,否则打印提示并以sys.exit(1)退出。这一步有效拦截了误输入的非 Medium 链接。 - 发起 HTTP 请求:通过
requests.get(url)拉取页面,随后调用res.raise_for_status():当响应状态码为 4xx/5xx 时该方法会抛出异常,避免把错误页面当正文解析。 - 构造解析树:用
BeautifulSoup(res.text, 'html.parser')把 HTML 文本解析成可遍历的文档树,供后续步骤提取内容。
3.purify():正则驱动的 HTML 标签清洗
purify()函数(scraping_medium.py)负责把带有标签的 HTML 片段还原为可读的纯文本:
def purify(text): rep = {"<br>": "\n", "<br/>": "\n", "<li>": "\n"} rep = dict((re.escape(k), v) for k, v in rep.items()) pattern = re.compile("|".join(rep.keys())) text = pattern.sub(lambda m: rep[re.escape(m.group(0))], text) text = re.sub('\<(.*?)\>', '', text) return text它的处理逻辑分两步:
- 特殊标签替换为换行:先把
<br>、<br/>、<li>这三种标签替换成\n,使得换行语义被保留下来。实现上用re.escape对替换键做转义后拼接成正则,再通过pattern.sub配合 lambda 完成映射替换,避免替换结果被后续正则再次误伤。 - 删除其余所有标签:用
re.sub(r'\<(.*?)\>', '', text)把剩下的所有尖括号标签(如<p>、<a>、<strong>等)整体移除,只保留纯文本内容。
这种「先保留换行语义、再删除标签」的两段式清洗,是静态页面文本抽取的常用手法,比简单粗暴地删除所有标签能得到更接近原文排版的结果。
4.collect_text():按 Medium 页面结构组装正文
collect_text()(scraping_medium.py)是整个脚本的信息组织核心,它依据 Medium 页面的 DOM 结构规律来提取并拼装内容:
def collect_text(soup): fin = f'url: {url}\n\n' main = (soup.head.title.text).split('|') global title title = main[0].strip() fin += f'Title: {title.upper()}\n{main[1].strip()}' header = soup.find_all('h1') j = 1 try: fin += '\n\nINTRODUCTION\n' for elem in list(header[j].previous_siblings)[::-1]: fin += f'\n{purify(str(elem))}' except: pass fin += f'\n\n{header[j].text.upper()}' for elem in header[j].next_siblings: if elem.name == 'h1': j+=1 fin += f'\n\n{header[j].text.upper()}' continue fin += f'\n{purify(str(elem))}' return fin这一段暗含了对 Medium 页面结构的几个假设,结合仓库中 scraped_articles 的示例输出可以一一印证:
- 标题与作者来源:Medium 页面
<title>的格式通常是「文章标题 | 出版物名称」。脚本用split('|')拆分,main[0]作为文章标题并转大写,main[1]作为作者/出版物署名行,拼成Title: xxx与署名两行。示例文件开头的Title: ONE MONTH INTO THE MLH FELLOWSHIP、by Kunal Kushwaha正是这一逻辑的直接产物。 - 导语(INTRODUCTION)提取:取页面中所有
<h1>标签,把第二个<h1>之前的所有兄弟节点反向遍历并清洗,作为 INTRODUCTION 段——即文章头部作者信息、阅读时长等元数据。 - 正文分节提取:以第二个
<h1>为起点向后遍历兄弟节点:遇到新的<h1>就换行输出该小节标题(转大写),普通节点则经purify()清洗后追加为正文。Medium 正文中的小标题本身就是<h1>,所以脚本天然地把文章切成多个「大写标题 + 段落」的小节。
示例输出中INTRODUCTION、WEEK 1、WEEK 2等小节标题全部大写、段落之间以空行分隔的排版,正是上述组装逻辑的运行结果。
5.save_file():自动建目录并按标题命名落盘
save_file()(scraping_medium.py)负责持久化:
def save_file(fin): if not os.path.exists('./scraped_articles'): os.mkdir('./scraped_articles') fname = './scraped_articles/' + '_'.join(title.split()) + '.txt' with open(fname, 'w', encoding='utf8') as outfile: outfile.write(fin) print(f'File saved in directory {fname}')- 若
scraped_articles目录不存在则自动创建; - 文件名取自文章标题,把标题中的空格替换为下划线(
'_'.join(title.split())),如One_month_into_the_MLH_Fellowship.txt; - 以
utf8编码写入,兼容文章中的中文、emoji 等非 ASCII 字符; - 落盘后打印保存路径,方便用户确认结果。
6. 驱动入口
文件末尾的标准入口(scraping_medium.py)把上述函数串成完整流水线:
if __name__ == '__main__': fin = collect_text(get_page()) save_file(fin)get_page()返回解析树 →collect_text()组装成纯文本 →save_file()写入磁盘,一条主线完成全部抓取任务。
输出文件格式规范
以仓库自带的 One_month_into_the_MLH_Fellowship.txt 为例,输出文件的固定结构为:
url: https://medium.com/<...>/<slug> Title: 文章标题(大写) by 作者署名 INTRODUCTION 导语与元信息段落 小节标题(大写) 小节正文段落 ...即:首行保留原始 URL 便于溯源;随后是标题与署名;INTRODUCTION段收纳文章头部信息;之后每个<h1>小标题独立成节、大写展示。这种「保留来源 + 结构化分节」的格式,让抓取结果既适合直接阅读,也方便后续做文本分析、语料库构建等二次加工。
边界条件与已知限制(基于源码实现)
根据代码逻辑,脚本存在以下客观边界,使用时需要留意:
- 仅接受
https?://medium.com/前缀的 URL:正则校验决定了它只能处理 Medium 主域下的文章,诸如子域名、自定义域或非 Medium 链接都会被拒绝并退出。 - 强依赖 Medium 的 DOM 结构:
<title>的「标题 | 出版物」格式、<h1>作为正文小标题等假设均针对 Medium 页面设计。若 Medium 调整页面结构,INTRODUCTION的提取可能因异常被try/except静默跳过,正文分节也可能出现偏差——脚本本身没有对这些分支做显式告警。 - 页面结构假设通过但内容缺失时会静默处理:
collect_text()中header[j]的索引依赖页面至少存在两个<h1>,极端情况下可能触发IndexError或异常分支。 - 纯文本输出,不保留图片与链接:
purify()会删除所有标签,因此文章中的图片、代码块高亮、超链接等富文本信息都会丢失,只留下线性文字。
这些限制并非缺陷,而是小型抓取脚本「够用即可」的典型取舍,也为读者预留了改造空间。
结合源码的扩展思路
在理解现有实现后,可以基于同一骨架做如下扩展(均不涉及修改仓库,仅作为思路参考):
- 批量抓取:把
input()改成从文件或参数列表循环读取多个 URL,复用get_page → collect_text → save_file流水线即可; - 增加请求头与限速:为
requests.get添加User-Agent与time.sleep,降低对目标站点的访问压力; - 保留 Markdown 结构:在
purify()的替换字典中加入<h1>→#、<strong>→**等映射,直接输出 Markdown; - 编码健壮性:对
main列表长度做防御性判断,避免<title>格式变化时越界。
小结
「Scraping Medium Articles」是一个小而完整的爬虫教学样例:requests负责取回页面,BeautifulSoup负责解析结构,正则负责清洗标签,最后以 UTF-8 落盘成结构化 TXT。通过本仓库的 README.md、scraping_medium.py 与 scraped_articles 示例三者对照,读者可以完整复现「输入 URL → 输出 TXT」的抓取链路,并以此为模板迁移到其他静态内容网站的文本抽取任务中。
- 示例工程
【免费下载链接】python-mini-projects
A collection of simple python mini projects to enhance your python skills
相关推荐
python-mini-projects 网页链接抓取实战:用 requests + BeautifulSoup 提取页面全部链接并保存
python mini projects 网页链接抓取实战:用 requests + BeautifulSoup 提取页面全部链接并保存 本技术指南以 pyth
示例工程python-mini-projects 实战:用 requests + BeautifulSoup 编写 Quote Scraper 名言爬虫并导出 CSV
python mini projects 实战:用 requests + BeautifulSoup 编写 Quote Scraper 名言爬虫并导出 CSV
示例工程python-mini-projects 实战:基于 BeautifulSoup 的 moneycontrol 金融新闻多页爬虫
python mini projects 实战:基于 BeautifulSoup 的 moneycontrol 金融新闻多页爬虫 本文以 python mini
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考