欢迎来到本教程,今天我们将学习如何用 Python 自动化脚本处理 SEO 批量数据。
如果你做过关键词整理、页面标题检查、Meta Description 缺失排查,应该很熟悉这种场景:几千行 CSV 数据摆在面前,Excel 能做,但做着做着就开始卡;筛选条件一多,公式复制错一格,结果就不可信;下周再来一批新数据,又要重复一遍。
坦白讲,SEO 数据处理最烦人的不是难,而是重复、琐碎、容易出错。Python 的价值就在这里:它不替你做 SEO 判断,但可以把清洗、合并、去重、分类、检测、导出报告这些机械动作稳定地跑完。
本教程适合初级到中级学习者。你不需要成为 Python 工程师,只要能看懂基础代码,就可以跟着这个步骤做出一个可复用的 SEO 批量数据处理脚本。
你将学会什么
完成本教程后,你会得到一个完整的小项目,能够处理常见 SEO 数据:
- 批量读取关键词 CSV、页面抓取 CSV、排名数据 CSV
- 清洗关键词空格、大小写、重复项
- 识别标题过长、标题缺失、描述缺失等页面问题
- 根据关键词意图做简单分类
- 合并多份 SEO 数据,生成可阅读的 Excel 报告
- 把脚本整理成以后可以反复使用的工具
这里有个重要提醒:自动化脚本不是魔法。它能帮你提升效率,但判断某个页面是否值得优化、关键词是否有商业价值,仍然需要你的 SEO 经验。我们要做的是把时间从重复劳动里拿回来,留给真正需要判断的部分。
前置准备:确保你已经装好这些工具
开始之前,你需要准备:
- Python 3.10 或更高版本
- 一个代码编辑器,推荐 VS Code
- 基础命令行操作能力
- 几份 CSV 文件,例如关键词表、URL 抓取表、排名表
如果你还没有安装依赖,打开终端,在项目文件夹里执行:
pip install pandas openpyxl本教程主要使用 pandas。它是处理表格数据非常常用的 Python 库,适合 CSV、Excel、批量清洗、数据合并等任务。
建议你创建这样的项目结构:
seo-python-demo/
├── data/
│ ├── keywords.csv
│ ├── pages.csv
│ └── rankings.csv
├── output/
└── seo_batch_report.py你可以把 data 理解成原始数据区,output 理解成结果区,seo_batch_report.py 是我们要编写的脚本。
示例数据应该长什么样
为了方便学习,我们先约定三份文件的字段。你不一定要完全一样,但字段名越规范,脚本越容易维护。
keywords.csv:关键词数据
keyword,volume,difficulty
python seo,500,35
seo automation,300,42
python seo,500,35
SEO Python Tutorial,150,28pages.csv:页面抓取数据
url,title,meta_description,h1,status_code
https://example.com/python-seo,Python SEO Guide,Learn Python SEO automation,Python SEO,200
https://example.com/old-page,,Old content page,Old Page,200
https://example.com/error-page,Error Page,,Error,404rankings.csv:排名数据
keyword,url,position
python seo,https://example.com/python-seo,8
seo automation,https://example.com/python-seo,15
python tutorial,https://example.com/python-tutorial,22真实工作里的数据通常更乱:字段名不统一、URL 带参数、关键词前后有空格、大小写混在一起。下一步很关键,我们先把这些脏数据清洗干净。
步骤一:读取 CSV,并做基础检查
在 seo_batch_report.py 中写入下面的完整代码。先不要急着加复杂逻辑,第一步只做读取和检查。
from pathlib import Path
import pandas as pd
BASE_DIR = Path(__file__).resolve().parent
DATA_DIR = BASE_DIR / 'data'
OUTPUT_DIR = BASE_DIR / 'output'
OUTPUT_DIR.mkdir(exist_ok=True)
keywords_path = DATA_DIR / 'keywords.csv'
pages_path = DATA_DIR / 'pages.csv'
rankings_path = DATA_DIR / 'rankings.csv'
keywords_df = pd.read_csv(keywords_path)
pages_df = pd.read_csv(pages_path)
rankings_df = pd.read_csv(rankings_path)
print('关键词数据:', keywords_df.shape)
print('页面数据:', pages_df.shape)
print('排名数据:', rankings_df.shape)
print(keywords_df.head())运行:
python seo_batch_report.py如果你能看到每份数据的行数和列数,说明读取成功。
完成这一步后,你已经建立了自动化处理的入口。很多新手一上来就想写复杂功能,结果报错后不知道哪里出了问题。我的建议是:每一步都先确认数据读进来了,再继续往下做。
步骤二:清洗关键词,避免后续统计失真
关键词清洗是 SEO 批量数据处理中最容易被低估的一步。
比如:
- python seo
- Python SEO
- python seo 后面带一个空格
人眼看起来差不多,但程序会把它们当成不同值。关键词去重、排名合并、分组统计都会被影响。
接着往下做,我们添加一个清洗函数:
def clean_keyword(value):
if pd.isna(value):
return ''
return str(value).strip().lower()
keywords_df['keyword_clean'] = keywords_df['keyword'].apply(clean_keyword)
rankings_df['keyword_clean'] = rankings_df['keyword'].apply(clean_keyword)
keywords_df = keywords_df[keywords_df['keyword_clean'] != '']
keywords_df = keywords_df.drop_duplicates(subset=['keyword_clean'])
print('清洗后的关键词数量:', len(keywords_df))这里做了三件事:
- 把空值转成空字符串,避免报错
- 去掉前后空格,并统一转成小写
- 根据 keyword_clean 去重
注意这个细节:我们没有直接覆盖原始 keyword 字段,而是新增 keyword_clean。这样做更安全,因为原始数据还能保留,后面检查问题时不容易说不清楚。
步骤三:规范 URL,减少重复页面
URL 也是 SEO 数据里的重灾区。同一个页面可能出现这些形式:
https://example.com/page
https://example.com/page/
https://example.com/page?utm_source=test是否要完全合并,取决于具体场景。对于基础审计,我们可以先做一个温和版规范化:去掉首尾空格,去掉末尾斜杠,但不强行删除参数。因为有些网站参数页确实需要单独分析。
def clean_url(value):
if pd.isna(value):
return ''
url = str(value).strip()
if url.endswith('/'):
url = url[:-1]
return url
pages_df['url_clean'] = pages_df['url'].apply(clean_url)
rankings_df['url_clean'] = rankings_df['url'].apply(clean_url)
pages_df = pages_df[pages_df['url_clean'] != '']
pages_df = pages_df.drop_duplicates(subset=['url_clean'])我不建议新手一开始就写太激进的 URL 规则,比如直接删除所有查询参数、统一 http 和 https。那样确实看起来干净,但可能把本来不同的页面合并掉。SEO 自动化最怕的不是慢一点,而是悄悄把数据处理错。
步骤四:检测页面 SEO 基础问题
现在我们来做页面审计。先从最常见、最容易自动化判断的项目开始:
- 状态码是否为 200
- Title 是否缺失
- Title 是否过长
- Meta Description 是否缺失
- H1 是否缺失
把下面的函数加入脚本:
def text_length(value):
if pd.isna(value):
return 0
return len(str(value).strip())
pages_df['title_length'] = pages_df['title'].apply(text_length)
pages_df['description_length'] = pages_df['meta_description'].apply(text_length)
pages_df['h1_length'] = pages_df['h1'].apply(text_length)
pages_df['issue_status'] = pages_df['status_code'].apply(lambda x: '非200状态' if x != 200 else '')
pages_df['issue_title_missing'] = pages_df['title_length'].apply(lambda x: 'Title缺失' if x == 0 else '')
pages_df['issue_title_long'] = pages_df['title_length'].apply(lambda x: 'Title可能过长' if x > 60 else '')
pages_df['issue_description_missing'] = pages_df['description_length'].apply(lambda x: 'Meta描述缺失' if x == 0 else '')
pages_df['issue_h1_missing'] = pages_df['h1_length'].apply(lambda x: 'H1缺失' if x == 0 else '')
issue_columns = [
'issue_status',
'issue_title_missing',
'issue_title_long',
'issue_description_missing',
'issue_h1_missing'
]
pages_df['issues'] = pages_df[issue_columns].apply(
lambda row: ';'.join([item for item in row if item]),
axis=1
)
pages_df['has_issue'] = pages_df['issues'].apply(lambda x: '是' if x else '否')关于 Title 长度,很多教程会给一个固定标准。这里我更愿意说得谨慎一点:60 个字符只是常用参考,不是绝对规则。搜索结果展示会受像素宽度、语言、设备影响。脚本的作用是帮你筛出“可能需要检查”的页面,而不是替你下最终结论。
步骤五:给关键词做简单意图分类
关键词意图分类可以做得很复杂,也可以先做一个够用的基础版。对于新手,我建议从规则分类开始,因为它透明、可解释,出错了也容易调整。
比如我们可以按词根判断:
- 包含 how、tutorial、guide,归为信息型
- 包含 price、cost、buy,归为交易型
- 包含 best、vs、review,归为对比型
- 其他归为待判断
def classify_intent(keyword):
kw = clean_keyword(keyword)
informational_words = ['how', 'tutorial', 'guide', 'what', 'why']
transactional_words = ['buy', 'price', 'cost', 'discount', 'coupon']
comparison_words = ['best', 'vs', 'review', 'compare']
if any(word in kw for word in transactional_words):
return '交易型'
if any(word in kw for word in comparison_words):
return '对比型'
if any(word in kw for word in informational_words):
return '信息型'
return '待判断'
keywords_df['intent'] = keywords_df['keyword_clean'].apply(classify_intent)这套规则对英文关键词更直接。如果你处理中文关键词,可以换成中文词根,例如:
- 怎么、如何、教程、方法:信息型
- 价格、多少钱、购买、优惠:交易型
- 哪个好、对比、评测、推荐:对比型
没有绝对答案。关键词意图和行业、页面类型、搜索结果页形态都有关系。我们先用脚本做初筛,再人工复核重点词,这才是更稳妥的工作流。
步骤六:合并关键词、排名和页面数据
进入下一环节,我们把三份数据串起来。这个步骤非常实用:你可以看到某个关键词对应哪个 URL、排名是多少、页面本身有没有 SEO 基础问题。
keyword_ranking_df = rankings_df.merge(
keywords_df[['keyword_clean', 'volume', 'difficulty', 'intent']],
on='keyword_clean',
how='left'
)
full_report_df = keyword_ranking_df.merge(
pages_df[['url_clean', 'title', 'title_length', 'meta_description', 'description_length', 'h1', 'status_code', 'issues', 'has_issue']],
on='url_clean',
how='left'
)这里的 how='left' 很重要。它表示以排名数据为主,即使关键词表或页面表里找不到对应记录,也保留排名数据。
你可能会问:为什么不直接用 inner join,只保留匹配成功的数据?
因为在 SEO 排查中,“匹配不上”本身就是线索。比如排名工具里有 URL,但抓取工具没有抓到,可能是抓取范围不完整,也可能是 URL 规范化规则不一致。保留这些异常,后面才有机会发现问题。
步骤七:生成 Excel 报告,而不是只导出一个 CSV
CSV 适合机器继续处理,Excel 更适合人查看。我们可以把不同结果放到不同 Sheet:
- full_report:关键词、排名、页面问题总表
- page_issues:存在 SEO 问题的页面
- keyword_summary:按意图统计关键词数量
继续添加:
page_issues_df = pages_df[pages_df['has_issue'] == '是'].copy()
keyword_summary_df = keywords_df.groupby('intent').agg(
keyword_count=('keyword_clean', 'count'),
avg_volume=('volume', 'mean'),
avg_difficulty=('difficulty', 'mean')
).reset_index()
output_file = OUTPUT_DIR / 'seo_batch_report.xlsx'
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
full_report_df.to_excel(writer, sheet_name='full_report', index=False)
page_issues_df.to_excel(writer, sheet_name='page_issues', index=False)
keyword_summary_df.to_excel(writer, sheet_name='keyword_summary', index=False)
print('报告已生成:', output_file)运行脚本后,打开 output 文件夹,你应该能看到 seo_batch_report.xlsx。
这一步完成后,你已经拥有一个基础但完整的 SEO 批量数据自动化脚本。它不花哨,但可维护、可扩展、能落地。
完整脚本汇总:可以直接复制运行
为了方便你对照,这里放一份完整版本。你可以先跑通,再根据自己的字段名调整。
from pathlib import Path
import pandas as pd
BASE_DIR = Path(__file__).resolve().parent
DATA_DIR = BASE_DIR / 'data'
OUTPUT_DIR = BASE_DIR / 'output'
OUTPUT_DIR.mkdir(exist_ok=True)
keywords_path = DATA_DIR / 'keywords.csv'
pages_path = DATA_DIR / 'pages.csv'
rankings_path = DATA_DIR / 'rankings.csv'
def clean_keyword(value):
if pd.isna(value):
return ''
return str(value).strip().lower()
def clean_url(value):
if pd.isna(value):
return ''
url = str(value).strip()
if url.endswith('/'):
url = url[:-1]
return url
def text_length(value):
if pd.isna(value):
return 0
return len(str(value).strip())
def classify_intent(keyword):
kw = clean_keyword(keyword)
informational_words = ['how', 'tutorial', 'guide', 'what', 'why']
transactional_words = ['buy', 'price', 'cost', 'discount', 'coupon']
comparison_words = ['best', 'vs', 'review', 'compare']
if any(word in kw for word in transactional_words):
return '交易型'
if any(word in kw for word in comparison_words):
return '对比型'
if any(word in kw for word in informational_words):
return '信息型'
return '待判断'
keywords_df = pd.read_csv(keywords_path)
pages_df = pd.read_csv(pages_path)
rankings_df = pd.read_csv(rankings_path)
keywords_df['keyword_clean'] = keywords_df['keyword'].apply(clean_keyword)
rankings_df['keyword_clean'] = rankings_df['keyword'].apply(clean_keyword)
keywords_df = keywords_df[keywords_df['keyword_clean'] != '']
keywords_df = keywords_df.drop_duplicates(subset=['keyword_clean'])
keywords_df['intent'] = keywords_df['keyword_clean'].apply(classify_intent)
pages_df['url_clean'] = pages_df['url'].apply(clean_url)
rankings_df['url_clean'] = rankings_df['url'].apply(clean_url)
pages_df = pages_df[pages_df['url_clean'] != '']
pages_df = pages_df.drop_duplicates(subset=['url_clean'])
pages_df['title_length'] = pages_df['title'].apply(text_length)
pages_df['description_length'] = pages_df['meta_description'].apply(text_length)
pages_df['h1_length'] = pages_df['h1'].apply(text_length)
pages_df['issue_status'] = pages_df['status_code'].apply(lambda x: '非200状态' if x != 200 else '')
pages_df['issue_title_missing'] = pages_df['title_length'].apply(lambda x: 'Title缺失' if x == 0 else '')
pages_df['issue_title_long'] = pages_df['title_length'].apply(lambda x: 'Title可能过长' if x > 60 else '')
pages_df['issue_description_missing'] = pages_df['description_length'].apply(lambda x: 'Meta描述缺失' if x == 0 else '')
pages_df['issue_h1_missing'] = pages_df['h1_length'].apply(lambda x: 'H1缺失' if x == 0 else '')
issue_columns = [
'issue_status',
'issue_title_missing',
'issue_title_long',
'issue_description_missing',
'issue_h1_missing'
]
pages_df['issues'] = pages_df[issue_columns].apply(
lambda row: ';'.join([item for item in row if item]),
axis=1
)
pages_df['has_issue'] = pages_df['issues'].apply(lambda x: '是' if x else '否')
keyword_ranking_df = rankings_df.merge(
keywords_df[['keyword_clean', 'volume', 'difficulty', 'intent']],
on='keyword_clean',
how='left'
)
full_report_df = keyword_ranking_df.merge(
pages_df[['url_clean', 'title', 'title_length', 'meta_description', 'description_length', 'h1', 'status_code', 'issues', 'has_issue']],
on='url_clean',
how='left'
)
page_issues_df = pages_df[pages_df['has_issue'] == '是'].copy()
keyword_summary_df = keywords_df.groupby('intent').agg(
keyword_count=('keyword_clean', 'count'),
avg_volume=('volume', 'mean'),
avg_difficulty=('difficulty', 'mean')
).reset_index()
output_file = OUTPUT_DIR / 'seo_batch_report.xlsx'
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
full_report_df.to_excel(writer, sheet_name='full_report', index=False)
page_issues_df.to_excel(writer, sheet_name='page_issues', index=False)
keyword_summary_df.to_excel(writer, sheet_name='keyword_summary', index=False)
print('关键词数据:', keywords_df.shape)
print('页面数据:', pages_df.shape)
print('排名数据:', rankings_df.shape)
print('报告已生成:', output_file)实践练习:把脚本改成适合你的网站
现在我们来做几个练习。不要只复制代码,真正掌握自动化脚本的方法,是把它改成你的工作流。
练习一:增加 Title 过短检测
很多页面 Title 不缺失,但只有一两个词,也可能不够清晰。你可以增加规则:小于 15 个字符标记为 Title 可能过短。
参考写法:
pages_df['issue_title_short'] = pages_df['title_length'].apply(lambda x: 'Title可能过短' if 0 < x < 15 else '')记得把 issue_title_short 加入 issue_columns。
练习二:识别排名在 11 到 20 的关键词
排名 11 到 20 的词通常值得重点看,因为它们已经接近第一页,但还没拿到足够曝光。你可以这样筛选:
near_page_one_df = full_report_df[
(full_report_df['position'] >= 11) &
(full_report_df['position'] <= 20)
].copy()然后把它导出到新的 Sheet:
near_page_one_df.to_excel(writer, sheet_name='near_page_one', index=False)练习三:给页面问题设置优先级
并不是所有问题都同等重要。404 页面、非 200 页面通常比 Title 过长更值得优先处理。你可以添加一个优先级字段:
def get_priority(row):
if row['status_code'] != 200:
return '高'
if row['issue_title_missing'] or row['issue_description_missing']:
return '中'
if row['issues']:
return '低'
return '无'
pages_df['priority'] = pages_df.apply(get_priority, axis=1)这就是自动化脚本真正有用的地方:它不只是列问题,还能帮你排序,让你知道先处理什么。
检查验收:跑完后你应该看到什么
完成所有步骤后,请按下面的清单检查:
- output 文件夹中生成了 seo_batch_report.xlsx
- full_report Sheet 中能看到关键词、URL、排名、页面标题、问题字段
- page_issues Sheet 中只保留存在问题的页面
- keyword_summary Sheet 中按意图统计了关键词数量
- 脚本重复运行不会报错,旧报告会被覆盖
- 原始 CSV 文件没有被修改
如果你做到这些,恭喜你完成了一个可复用的 Python SEO 自动化基础项目。
常见报错与处理方法
报错:No such file or directory
通常是文件路径不对。检查 data 文件夹是否和 seo_batch_report.py 在同一级目录,CSV 文件名是否完全一致。
报错:KeyError: keyword
这说明 CSV 里没有 keyword 这个字段。打开文件检查表头,可能是 Keyword、关键词、keywords。你可以统一改 CSV 表头,也可以在代码里适配字段名。
中文乱码怎么办
如果 CSV 是从某些工具导出的,可能需要指定编码:
pd.read_csv(keywords_path, encoding='utf-8-sig')如果还是不行,可以尝试 gbk,但不要盲目修改。先确认你的文件实际编码。
Excel 打不开或文件损坏
常见原因是脚本运行时 Excel 文件正处于打开状态。关闭 Excel 后重新运行脚本。
FAQ:关于 Python 自动化处理 SEO 数据的几个真实问题
需要学到什么程度才能用于 SEO 工作?
不需要学完整个 Python 体系。你先掌握文件读取、pandas 表格处理、条件筛选、数据合并、Excel 导出,就足够覆盖很多 SEO 日常任务。
Excel 已经能做,为什么还要用 Python?
如果只是几十行数据,Excel 更快。如果是几千行、几万行,或者每周都要重复同样流程,Python 更稳定。关键不是炫技,而是减少重复劳动和人为错误。
这个脚本能不能直接用于大型网站?
可以作为起点,但大型网站还需要考虑更多因素,比如分页抓取、Canonical、robots、重定向链、日志文件、索引状态、模板聚类等。不要一口吃成胖子,先把基础数据流跑顺。
是否可以接入 Ahrefs、Semrush、Search Console 数据?
可以。只要能导出 CSV 或通过 API 获取数据,就可以用类似思路处理。建议新手先用 CSV 练熟,再接 API。API 会多出鉴权、限流、字段变化等问题。
下一步怎么学更稳
如果你已经跑通这个教程,下一步可以沿着三条线继续扩展:
- 数据源扩展:加入 Search Console 查询词、点击、展示、CTR 数据
- 页面审计扩展:检查 Canonical、图片 Alt、内链数量、重复标题
- 报告自动化:按日期生成文件名,定时运行,自动发送邮件
我认为学习 Python 自动化 SEO,最好的路径不是先啃厚书,而是围绕真实任务一点点加功能。今天先清洗关键词,明天合并排名,后天生成报告。每完成一个小脚本,你对 SEO 数据的掌控感都会更强。
到这里,你已经完成了从数据读取、清洗、检测、合并到报告导出的完整流程。接下来,把示例 CSV 换成你的真实数据,仔细按照步骤调整字段名和规则。真正的学习,会从你第一次处理自己的脏数据开始。
