循环遍历URL高效爬取美国各州历年人口数据的代码调试
1991-2018年美国各州人口爬虫代码修正
原代码核心逻辑疏漏
- 变量名重复覆盖:外层年份遍历使用
i作为循环变量,内层提取州名、人口节点时又重复使用i作为循环变量,直接覆盖外层年份值,导致年份匹配完全错乱 - 年份赋值逻辑错误:通过
append向合并后的州-人口表单独插入一行仅含年份的记录,会导致所有州对应行的Year字段为空,最终多出一行无州名、无人口值的无效数据 - 结果容器初始化位置错误:用于存储各年数据的
df_lists被定义在年份循环内部,每次循环都会被重置为空列表,后续遍历空列表追加数据的代码完全不会执行,最终无法留存任何抓取结果 - 缺少反爬适配:直接发起无标识的HTTP请求很容易被站点反爬规则拦截,返回非目标页面内容
- 冗余代码:开头定义的空DataFrame
df全程未参与数据读写,属于无效代码
修正后可运行代码
import numpy as np import requests from bs4 import BeautifulSoup import pandas as pd # 生成待抓取年份列表 years = np.arange(1991, 2019).tolist() base_url = 'https://fred.stlouisfed.org/release/tables?rid=118&eid=259194' # 加请求头模拟浏览器访问,避免被反爬拦截 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' } # 初始化存储所有年份数据的列表,放在循环外避免被重置 df_lists = [] for year in years: # 拼接对应年份的URL full_link = f'{base_url}&od={year}-01-01#' page = requests.get(full_link, headers=headers) soup = BeautifulSoup(page.text, 'lxml') # 提取州名 states = soup.find_all('span', class_='fred-rls-elm-nm') states_list = [s.text for s in states] states_df = pd.DataFrame(states_list, columns=['State']) states_df['index'] = np.arange(len(states_df)) # 提取人口值 pops = soup.find_all('td', class_='fred-rls-elm-vl-td') pop_list = [p.text for p in pops] pop_df = pd.DataFrame(pop_list, columns=['Population']) # 清洗人口数据 pop_df['Population'] = pop_df['Population'].replace('\n', "", regex=True).str.strip() pop_df['Population'] = pop_df['Population'].replace(',', "", regex=True) pop_df = pop_df.apply(pd.to_numeric, errors='ignore') # 每3个值取第一个,对应人口绝对值,跳过变动值、变动率 pop_df = pop_df.iloc[::3, :] pop_df['Population'] = (pop_df['Population'] * 1000).astype(int) pop_df['index'] = np.arange(len(pop_df)) # 合并州名和人口数据 full_df = pd.merge(states_df, pop_df, how='left', on=['index']) # 给当前年份所有行统一赋值年份 full_df['Year'] = year # 只保留需要的三列 full_df = full_df[['State', 'Year', 'Population']] # 追加到结果列表 df_lists.append(full_df) print(f"已完成{year}年数据抓取,共获取{len(full_df)}条记录") # 合并所有年份的数据为最终DataFrame final_df = pd.concat(df_lists, ignore_index=True) print(final_df)
关键修正点说明
- 把存储结果的
df_lists移到年份循环外初始化,每次循环抓取完当年数据直接追加到列表,不会被重置 - 统一循环变量命名,外层年份遍历用
year作为变量名,内层DOM遍历用s/p等独立变量名,避免变量值覆盖 - 去掉错误的单行追加年份逻辑,合并完当年的州、人口数据后,直接给整个DataFrame的
Year列赋值当前年份,保证每行数据都能匹配到正确年份 - 新增请求头配置,模拟正常浏览器访问,降低被反爬拦截的概率
- 所有年份抓取完成后,用
pd.concat一次性合并所有子DataFrame,直接得到包含State/Year/Population三列的规整数据集 - 删除无实际作用的初始空DataFrame定义,精简冗余逻辑
内容的提问来源于stack exchange,提问作者user2813606
相关产品推荐
相关产品推荐

