1. 项目概述与核心价值最近在整理个人数据分析项目时我重新审视了一个几年前做的、但至今仍有很强参考价值的案例用Python爬取并分析福利彩票的历史开奖数据。这个项目听起来简单但实操起来从数据获取、清洗到初步分析每一步都藏着不少门道尤其是对于刚接触Python爬虫和数据化分析的朋友来说它是一个绝佳的练手项目。为什么这么说呢首先数据源相对公开、结构化程度高降低了入门门槛其次整个过程涵盖了网络请求、HTML解析、数据存储、清洗分析和可视化等数据科学工作流的完整环节最后通过对这些看似随机数字的分析你可以实践频率统计、趋势观察、简单预测模型等基础数据分析方法趣味性和实用性兼备。这个项目适合所有对Python爬虫和数据分析感兴趣的初学者和有一定基础的开发者。你不需要是数学或统计学专家只需要有Python基础语法知识跟着步骤一步步来就能亲手构建一个从互联网“采集”数据并转化为洞察的小系统。我们将使用最经典的requests库来获取网页用BeautifulSoup或lxml来解析HTML用pandas来整理和分析数据最后可能还会用matplotlib或seaborn画几张图看看规律。整个项目的核心不在于预测下一期彩票号码——那是不切实际的——而在于完整地实践一次数据驱动的分析流程理解数据从源头到结论的全过程。2. 项目整体设计与思路拆解2.1 目标定义与数据源选择在动手写任何一行代码之前明确目标至关重要。我们这个项目的终极目标是获取历史开奖数据并进行初步的数据化分析观察其分布特征。因此我们需要一个稳定、历史数据齐全、页面结构清晰的福利彩票数据网站作为数据源。经过对比国内多个官方授权的彩票资讯网站都提供历史数据查询功能它们通常以表格形式呈现包含期号、开奖日期、红球号码、蓝球号码等信息结构非常规整非常适合爬取。这里有一个非常重要的原则选择数据源时务必确认其公开性和合法性仅用于个人学习与技术研究严格遵守相关法律法规和网站的使用条款Robots协议。我们的所有操作都应模拟正常用户的浏览器访问避免对目标服务器造成过大压力。这次项目我们假设以一个结构清晰的静态历史开奖列表页为例进行讲解。2.2 技术栈选型与工具准备工欲善其事必先利其器。针对这个项目我们选择一套成熟、稳定、学习资料丰富的技术组合网络请求库requests为什么选它requests库是Python HTTP客户端库的“事实标准”其API设计极其人性化简单几行代码就能完成GET、POST请求处理Cookies、Headers等也非常方便。相比于原生urllib它更Pythonic代码更简洁。核心任务负责向目标网址发送HTTP请求并将服务器返回的HTML内容获取到本地。HTML解析库BeautifulSoup4(配合lxml解析器)为什么选它BeautifulSoup提供了灵活的、面向对象的的方式来解析HTML和XML文档。即使页面结构稍有变化其强大的查找方法如find,find_all,select也能让我们轻松定位到所需的数据表格。搭配lxml解析器在解析速度上也有不错的表现。核心任务从requests下载的杂乱HTML字符串中精准地提取出期号、日期、号码等结构化数据。数据处理与分析库pandas为什么选它pandas是Python数据分析的基石。它提供的DataFrame数据结构可以看作是一个功能强大的电子表格能方便地进行数据清洗、转换、聚合和统计分析。将爬取的数据存入DataFrame后续所有分析工作都会变得异常高效。核心任务存储、清洗爬取到的数据并计算各类统计指标如号码出现频率、奇偶比、区间分布等。数据可视化库matplotlib/seaborn为什么选它们一图胜千言。matplotlib是基础的绘图库功能全面seaborn基于matplotlib提供了更高级的统计图形接口和更美观的默认样式。两者结合可以轻松绘制直方图、折线图、热力图等直观展示号码分布规律。核心任务将pandas分析出的统计结果以图表形式直观呈现。开发环境推荐Jupyter Notebook或VS CodeJupyter Notebook:特别适合数据分析类项目可以分段执行代码、即时查看结果如图表并穿插Markdown文本记录分析思路交互性极强。VS Code:功能全面的代码编辑器配合Python插件调试、代码提示等功能非常完善适合编写完整的脚本。环境配置提示确保你的Python环境建议使用Python 3.7及以上版本中已通过pip install requests beautifulsoup4 pandas matplotlib seaborn安装好上述库。如果使用VS Code记得配置好Python解释器路径。2.3 核心思路流程图整个项目的执行脉络可以概括为以下步骤这构成了我们代码编写的骨架1. 目标分析确定要爬取的网站URL分析其页面结构表格所在HTML标签。 2. 发送请求使用requests.get()模拟浏览器访问获取页面HTML源码。 3. 解析内容使用BeautifulSoup解析HTML定位到数据表格遍历每一行tr标签。 4. 提取数据从每一行中提取出各个单元格td标签内的文本期号、日期、号码等。 5. 数据清洗处理提取到的文本如去除空格、拆分号码字符串、转换数据类型字符串转数字、日期。 6. 存储数据将清洗后的数据存入pandas DataFrame也可选择保存为CSV或Excel文件以便后续使用。 7. 数据分析利用pandas进行统计计算如各号码出现次数、频率、最近N期的走势等。 8. 结果可视化使用matplotlib/seaborn绘制统计图表直观展示分析结果。3. 核心细节解析与实操要点3.1 网页结构分析与数据定位这是爬虫成功的第一步也是最关键的一步。你需要像侦探一样仔细审查目标网页的HTML结构。使用开发者工具在浏览器Chrome/Firefox中打开目标历史开奖页面按下F12键打开开发者工具。定位数据表格使用元素检查工具通常是一个鼠标箭头图标点击页面上的开奖数据表格。开发者工具会自动高亮显示对应的HTML代码。分析标签结构通常数据会放在一个table标签内。每一期开奖数据对应一个tr表格行标签。每一个数据项如期号、日期、红球1、红球2…对应一个td表格单元格或th表头单元格标签。寻找唯一标识观察table或其父级div标签是否具有唯一的id或class属性。例如你可能会发现table idkj_table或div classhistory-list。利用这些属性可以让我们在复杂的HTML中精准定位到目标数据区域避免抓取到无关信息。注意网站前端结构可能会改版。今天能用的选择器明天可能就失效了。因此你的解析代码应尽量使用具有唯一性、语义化的属性并做好异常处理。一个实用的技巧是将首次成功爬取到的HTML源码保存到一个本地.html文件这样即使网站暂时无法访问你也能基于本地文件继续开发和调试解析逻辑。3.2 使用Requests库的注意事项与反爬策略应对requests库虽然简单但直接使用可能会触发网站的反爬虫机制。我们需要让我们的请求看起来更像一个真实的浏览器。设置请求头Headers这是最重要的步骤。服务器通过User-Agent等字段来判断访问者是谁。我们需要添加一个常见的浏览器User-Agent。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/lottery/history.html # 替换为实际网址 response requests.get(url, headersheaders)处理编码问题获取到的response.text可能因编码问题出现乱码。通常可以查看response.encoding属性或根据网页源码中的meta charset标签手动设置response.encoding utf-8或gbk。控制访问频率在循环爬取多页数据时务必在请求之间添加延时例如time.sleep(2)这是网络爬虫的道德和法律底线能有效避免因请求过快导致IP被封锁即遇到429 Too Many Requests错误。import time for page in range(1, 11): # 构造每一页的URL page_url f{base_url}?page{page} resp requests.get(page_url, headersheaders) # 解析本页数据... time.sleep(1) # 每爬一页休息1秒处理会话与Cookies如果网站需要登录或存在简单的会话验证可以使用requests.Session()对象来保持Cookies模拟登录状态。3.3 使用BeautifulSoup进行精准数据提取拿到HTML字符串后BeautifulSoup登场。创建Soup对象建议指定lxml解析器效率更高。from bs4 import BeautifulSoup soup BeautifulSoup(response.text, lxml)定位表格使用find()或find_all()方法结合之前分析出的标签和属性。# 假设表格有idkj_table data_table soup.find(table, idkj_table) # 或者通过class查找 # data_table soup.find(table, class_history-table)提取行与列找到表格后获取所有行然后遍历每一行提取单元格数据。data_list [] # 通常第一行是表头可能需要跳过: rows data_table.find_all(tr)[1:] rows data_table.find_all(tr) for row in rows: cols row.find_all(td) # 获取该行所有单元格 if cols: # 确保不是空行 # 假设列顺序为期号日期号码串销售额... period cols[0].text.strip() date cols[1].text.strip() # 号码可能是一个用空格或逗号分隔的字符串如01 05 12 23 24 30 15 number_str cols[2].text.strip() # 将号码字符串拆分成列表 numbers number_str.split() # 存入一个字典然后添加到总列表 data_list.append({ 期号: period, 开奖日期: date, 号码列表: numbers })实操心得strip()方法用于去除字符串两端的空白字符包括空格、换行符等这是数据清洗的第一步非常重要。对于号码需要观察其分隔符是空格、逗号还是其他相应调整split()的参数。4. 实操过程与核心环节实现4.1 构建完整的单页爬取函数我们将上述步骤封装成一个函数提高代码的复用性和可读性。import requests from bs4 import BeautifulSoup import pandas as pd import time def fetch_single_page(url, headers): 爬取单页彩票历史数据 Args: url (str): 目标页面URL headers (dict): 请求头 Returns: list: 包含多期开奖数据字典的列表 try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() # 如果状态码不是200抛出HTTPError异常 # 假设网页编码是utf-8根据实际情况调整 resp.encoding utf-8 soup BeautifulSoup(resp.text, lxml) # 这里需要根据实际网页结构调整选择器 table soup.find(table, {class: kj-table}) # 示例 if not table: print(f未在页面 {url} 中找到数据表格) return [] data_list [] # 跳过表头行 for row in table.find_all(tr)[1:]: cols row.find_all(td) # 确保这一行有足够的列数例如至少7列期号、日期、6个红球、1个蓝球 if len(cols) 7: period cols[0].text.strip() date cols[1].text.strip() # 假设红球在第2-7列蓝球在第8列 red_balls [cols[i].text.strip().zfill(2) for i in range(2, 8)] # zfill(2)保证号码是两位如01 blue_ball cols[7].text.strip().zfill(2) record { 期号: period, 开奖日期: date, 红球: .join(red_balls), # 用空格连接方便查看 蓝球: blue_ball, 红球列表: red_balls, # 保留列表形式便于后续分析 蓝球数值: int(blue_ball) # 转换为整数便于计算 } data_list.append(record) print(f从 {url} 成功提取 {len(data_list)} 条记录。) return data_list except requests.exceptions.RequestException as e: print(f请求 {url} 时发生错误: {e}) return [] except Exception as e: print(f解析 {url} 时发生未知错误: {e}) return [] # 使用示例 headers {User-Agent: 你的浏览器User-Agent} test_url https://www.example.com/lottery/history/page1.html # 替换为真实URL page_data fetch_single_page(test_url, headers)4.2 实现多页爬取与数据整合历史数据通常分页显示。我们需要分析分页URL的规律然后循环调用单页爬取函数。def fetch_multiple_pages(base_url_pattern, start_page, end_page, headers, delay1): 爬取多页数据 Args: base_url_pattern (str): 带页码占位符的URL模式如 https://.../page_{}.html start_page (int): 起始页码 end_page (int): 结束页码包含 headers (dict): 请求头 delay (int): 页间延迟秒数 Returns: list: 所有页面的数据总和 all_data [] for page_num in range(start_page, end_page 1): current_url base_url_pattern.format(page_num) print(f正在爬取第 {page_num} 页: {current_url}) page_data fetch_single_page(current_url, headers) if page_data: all_data.extend(page_data) else: print(f第 {page_num} 页数据为空或爬取失败。) # 遵守爬虫礼仪添加延迟 time.sleep(delay) print(f所有页面爬取完成共获取 {len(all_data)} 条开奖记录。) return all_data # 使用示例假设URL是 /history_1.html, /history_2.html ... base_pattern https://www.example.com/lottery/history_{}.html all_lottery_data fetch_multiple_pages(base_pattern, 1, 10, headers, delay2)4.3 数据清洗与Pandas DataFrame构建爬取到的原始数据列表需要被转换成pandas DataFrame并进行清洗。import pandas as pd from datetime import datetime # 1. 将数据列表转换为DataFrame df pd.DataFrame(all_lottery_data) # 2. 查看数据概览 print(df.head()) print(df.info()) print(df.describe()) # 3. 数据清洗 # 3.1 处理日期列将字符串转换为datetime类型 df[开奖日期] pd.to_datetime(df[开奖日期], format%Y-%m-%d, errorscoerce) # errorscoerce会将解析失败的日期转为NaTNot a Time方便后续检查 # 3.2 检查缺失值 print(f缺失值统计:\n{df.isnull().sum()}) # 如果有缺失根据情况处理例如删除缺失行df df.dropna() # 3.3 去重基于期号 df df.drop_duplicates(subset[期号], keepfirst) # 3.4 排序按日期或期号升序 df df.sort_values(by开奖日期).reset_index(dropTrue) # 3.5 拆分红球列表为单独的列便于分析 # 假设红球列表是Python列表格式 red_ball_df df[红球列表].apply(pd.Series) red_ball_df.columns [f红球{i1} for i in range(red_ball_df.shape[1])] # 将拆分后的红球列合并回原DataFrame df pd.concat([df, red_ball_df], axis1) # 4. 保存到本地文件CSV df.to_csv(lottery_history.csv, indexFalse, encodingutf-8-sig) # utf-8-sig支持Excel中文 print(数据已清洗并保存至 lottery_history.csv)4.4 基础数据分析与统计有了干净的DataFrame我们就可以开始分析了。# 1. 基本统计各号码出现次数 # 合并所有红球号码到一个列表 all_red_numbers [] for col in [f红球{i1} for i in range(6)]: all_red_numbers.extend(df[col].tolist()) # 使用pandas的value_counts进行统计 red_ball_counts pd.Series(all_red_numbers).value_counts().sort_index() blue_ball_counts df[蓝球数值].value_counts().sort_index() print(红球号码出现次数统计:) print(red_ball_counts.head(10)) print(\n蓝球号码出现次数统计:) print(blue_ball_counts.head()) # 2. 计算频率 red_ball_freq (red_ball_counts / len(df) * 100).round(2) # 每期出现概率% blue_ball_freq (blue_ball_counts / len(df) * 100).round(2) print(\n红球号码出现频率%:) print(red_ball_freq.head()) # 3. 分析奇偶比、大小比以红球为例 df[红球奇偶个数] df[[红球1,红球2,红球3,红球4,红球5,红球6]].applymap(lambda x: int(x) % 2).sum(axis1) # 假设红球范围1-33大小以17为界 df[红球大小个数] df[[红球1,红球2,红球3,红球4,红球5,红球6]].applymap(lambda x: 1 if int(x) 17 else 0).sum(axis1) odd_even_ratio df[红球奇偶个数].value_counts().sort_index() size_ratio df[红球大小个数].value_counts().sort_index() print(\n每期红球奇数个数分布:) print(odd_even_ratio) print(\n每期红球大数(17)个数分布:) print(size_ratio)4.5 数据可视化呈现将统计结果用图表展示更加直观。import matplotlib.pyplot as plt import seaborn as sns # 设置中文字体和图表样式 plt.rcParams[font.sans-serif] [SimHei, DejaVu Sans] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负号 sns.set_style(whitegrid) # 1. 绘制红球号码出现次数柱状图 plt.figure(figsize(15, 6)) red_ball_counts.plot(kindbar) plt.title(历史开奖红球号码出现次数统计) plt.xlabel(红球号码) plt.ylabel(出现次数) plt.xticks(rotation0) plt.tight_layout() plt.show() # 2. 绘制蓝球号码出现次数饼图 plt.figure(figsize(10, 10)) blue_ball_counts.plot(kindpie, autopct%1.1f%%, startangle90) plt.title(蓝球号码出现次数占比) plt.ylabel() # 隐藏y轴标签 plt.show() # 3. 绘制奇偶比分布图 plt.figure(figsize(10, 5)) plt.subplot(1, 2, 1) odd_even_ratio.plot(kindbar, colorskyblue) plt.title(每期红球奇数个数分布) plt.xlabel(奇数个数) plt.ylabel(期数) plt.subplot(1, 2, 2) size_ratio.plot(kindbar, colorlightcoral) plt.title(每期红球大数(17)个数分布) plt.xlabel(大数个数) plt.ylabel(期数) plt.tight_layout() plt.show() # 4. 热力图展示红球两两组合的出现频率进阶 # 此部分代码较复杂涉及组合统计可作为延伸练习。基本思路是生成所有红球两两组合统计它们在历史各期中同时出现的次数。5. 常见问题与排查技巧实录在爬虫开发和数据分析过程中你几乎一定会遇到下面这些问题。这里记录了我的排查思路和解决方法。5.1 网络请求相关问题问题现象可能原因排查与解决思路ConnectionError/Timeout网络不稳定、目标服务器不可用、请求超时时间太短1. 检查网络连接。2. 增加timeout参数值如timeout15。3. 使用try-except捕获异常并加入重试机制如requests.adapters.HTTPAdapter设置重试。HTTP 403 Forbidden网站识别出爬虫拒绝了请求1.检查并完善headers特别是User-Agent可以尝试更换其他浏览器的User-Agent字符串。2. 检查是否需要携带特定的Referer或Cookie。3. 考虑使用requests.Session()维持会话。4. 如果网站反爬严格可能需要研究更复杂的策略如验证码识别但本项目应避免合规第一。HTTP 429 Too Many Requests请求频率过高触发服务器限流这是最重要的注意事项1.立即、显著地降低请求频率增加time.sleep()的延时。2. 如果爬取量很大考虑使用代理IP池但个人学习项目通常不需要且需注意代理的合法性。3. 分析网站是否有API接口直接调用API比爬网页更友好。返回乱码响应内容的编码与requests默认解码编码不匹配1. 查看response.encoding属性。2. 查看网页HTML源码中meta charset...标签。3. 手动设置response.encoding utf-8或gbk。4. 使用response.content.decode(正确的编码)。实操心得对于429错误一个简单的策略是“指数退避重试”。当捕获到该错误时等待一段时间如2秒再重试如果还失败等待时间加倍4秒、8秒…直到成功或达到最大重试次数。这体现了对目标服务器的尊重。5.2 数据解析与提取问题问题现象可能原因排查与解决思路soup.find()返回None选择器写错了或者网页结构已更新1.重新检查网页结构使用浏览器开发者工具确认目标标签及其属性是否变化。2. 尝试更通用的选择器如soup.find(table)先找到所有表格再筛选。3. 使用soup.select()配合CSS选择器有时更灵活。4.将response.text保存为本地HTML文件用编辑器打开仔细核对。提取到的数据是空列表或部分缺失1. 表格有动态加载JavaScript。2. 数据不在table里可能在divspan等其他标签中。1. 查看网页源代码CtrlU如果数据不在源码中说明是JS动态渲染的。此时requests无法直接获取需要考虑使用Selenium或Playwright等浏览器自动化工具。2. 调整解析逻辑根据实际的HTML标签结构来写。例如数据可能在div classnumber下的多个span里。提取的文本包含多余空格、换行符HTML标签内文本的格式问题善用.text.strip()。对于更复杂的清理可以使用.get_text(stripTrue)或正则表达式re.sub(r\s, , text)将多个空白字符替换为一个空格。数据类型错误如该是数字的却是字符串提取后未进行类型转换在存入DataFrame或进行分析前使用int(),float(),pd.to_datetime()等进行强制类型转换并做好异常处理try-except。5.3 数据分析与存储问题问题现象可能原因排查与解决思路pandas读取或保存CSV文件时中文乱码编码问题保存时使用df.to_csv(file.csv, indexFalse, encodingutf-8-sig)。utf-8-sig会添加BOM头兼容Windows系统下的Excel。读取时指定相同编码pd.read_csv(file.csv, encodingutf-8-sig)。数据分析结果不符合常识如号码出现次数为0数据清洗不彻底存在无效数据如“--”、“未开奖”等1. 在数据提取和清洗阶段增加数据有效性校验。2. 使用df.replace()或条件筛选df[df[号码] ! --]清除无效值。3. 在统计前使用df.dropna()删除缺失行。可视化图表中文显示为方框matplotlib未配置中文字体在绘图前运行以下代码路径需替换为系统存在的字体文件import matplotlib.pyplot as pltplt.rcParams[font.sans-serif] [SimHei] # 黑体plt.rcParams[axes.unicode_minus] False内存不足或程序运行缓慢数据量极大时1. 一次性加载所有数据到列表。2. 数据分析操作效率低。1. 考虑分批次爬取和保存每爬取一定数量就写入一次文件。2. 使用pandas的向量化操作避免在DataFrame上使用for循环。3. 对于超大数据考虑使用dask库或数据库如SQLite进行存储和查询。5.4 项目伦理与法律边界提醒这是必须单独强调的部分。在进行任何网络爬取项目时请务必牢记遵守Robots协议访问目标网站的/robots.txt文件如https://example.com/robots.txt查看是否允许爬虫抓取你目标的数据路径。User-agent: *和Disallow: /意味着禁止所有爬虫。尊重网站服务器务必设置合理的请求延迟如time.sleep(2)避免高频访问对对方服务器造成负担这既是技术道德也能防止你的IP被封锁。明确数据用途本项目及本指南所获数据仅限于个人学习、研究和教育目的。严禁将数据用于任何商业用途、赌博预测或任何违法活动。关注数据版权公开不等于免费。某些网站的数据可能受版权保护批量爬取并重新分发可能涉及侵权。不爬取个人信息和敏感数据这是红线中的红线。这个Python数据化分析项目从爬取福利彩票数据入手完整走了一遍数据获取、处理、分析和可视化的流程。它更像一个“教学样本”其价值不在于分析结果本身而在于掌握这套方法。当你熟悉了这个流程完全可以将其迁移到其他公开数据源比如天气数据、股票公开信息、电影评分、电商商品评论需遵守平台规则等等去解决实际工作中遇到的问题。爬虫和数据分析是赋能工具用对地方才能创造价值。