数据采集作业2 102302111 海米沙
第二次作业
作业一
1)题目:要求在中国气象网(http://www.weather.com.cn)给定城市集的7日天气预报,并保存在数据库。
– 输出信息:
Gitee文件夹链接

1.1 代码
`
import requests
import sqlite3
import re
from bs4 import BeautifulSoup
class WeatherCrawler:
def init(self, database_name='weather_data.db'):
self.database_name = database_name
self.base_url = "http://www.weather.com.cn"
self.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',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
self.init_database()
def init_database(self):
"""初始化数据库"""
conn = sqlite3.connect(self.database_name)
cursor = conn.cursor()
# 创建天气数据表
cursor.execute('''
CREATE TABLE IF NOT EXISTS weather_forecast (
id INTEGER PRIMARY KEY AUTOINCREMENT,
city_name TEXT NOT NULL,
date_str TEXT NOT NULL,
date_desc TEXT,
weather_info TEXT,
temperature TEXT,
crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
print("数据库初始化完成")
def get_city_code(self, city_name):
"""获取城市代码"""
city_codes = {
'北京': '101010100',
'上海': '101020100',
'广州': '101280101',
'深圳': '101280601',
'杭州': '101210101',
'南京': '101190101',
'武汉': '101200101',
'成都': '101270101',
'西安': '101110101',
'天津': '101030100'
}
return city_codes.get(city_name, '101010100') # 默认返回北京代码
def crawl_weather_data(self, city_name):
"""爬取指定城市的7日天气预报"""
city_code = self.get_city_code(city_name)
url = f"{self.base_url}/weather/{city_code}.shtml"
try:
response = requests.get(url, headers=self.headers, timeout=10)
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')
# 解析7日天气预报数据
weather_data = []
# 方法1: 通过CSS选择器获取天气数据
weather_items = soup.select('.tqtongji1 ul li, .t clearfix li')
if not weather_items:
# 方法2: 尝试其他选择器
weather_items = soup.select('li[class^="sky"]')
if not weather_items:
print("未找到天气数据,尝试解析HTML结构...")
# 打印部分HTML用于调试
print(response.text[:1000])
return []
for i, item in enumerate(weather_items[:7]): # 只取前7天
try:
# 提取日期
date_elem = item.select_one('h1')
date_str = date_elem.get_text().strip() if date_elem else f"第{i+1}天"
# 提取天气信息
weather_elem = item.select_one('.wea')
weather_info = weather_elem.get_text().strip() if weather_elem else "未知"
# 提取温度
temp_elem = item.select_one('.tem')
temperature = temp_elem.get_text().strip() if temp_elem else "未知"
# 清理数据
temperature = re.sub(r'\s+', '', temperature)
weather_data.append({
'city_name': city_name,
'date_str': date_str,
'weather_info': weather_info,
'temperature': temperature
})
except Exception as e:
print(f"解析第{i+1}天数据时出错: {e}")
continue
return weather_data
except Exception as e:
print(f"爬取{city_name}天气数据时出错: {e}")
return []
def save_to_database(self, weather_data):
"""将天气数据保存到数据库"""
if not weather_data:
print("没有数据可保存")
return False
conn = sqlite3.connect(self.database_name)
cursor = conn.cursor()
try:
for data in weather_data:
cursor.execute('''
INSERT INTO weather_forecast (city_name, date_str, weather_info, temperature)
VALUES (?, ?, ?, ?)
''', (data['city_name'], data['date_str'], data['weather_info'], data['temperature']))
conn.commit()
print(f"成功保存 {len(weather_data)} 条天气数据到数据库")
return True
except Exception as e:
print(f"保存到数据库时出错: {e}")
conn.rollback()
return False
finally:
conn.close()
def query_weather_data(self, city_name=None):
"""从数据库查询天气数据"""
conn = sqlite3.connect(self.database_name)
cursor = conn.cursor()
if city_name:
cursor.execute('''
SELECT city_name, date_str, weather_info, temperature, crawl_time
FROM weather_forecast
WHERE city_name = ?
ORDER BY crawl_time DESC, id ASC
''', (city_name,))
else:
cursor.execute('''
SELECT city_name, date_str, weather_info, temperature, crawl_time
FROM weather_forecast
ORDER BY crawl_time DESC, city_name, id ASC
''')
results = cursor.fetchall()
conn.close()
return results
def print_weather_table(self, weather_data):
"""以表格形式打印天气数据"""
if not weather_data:
print("没有天气数据可显示")
return
print("\n" + "="*80)
print(f"{'序号':<4} {'地区':<8} {'日期':<15} {'天气信息':<30} {'温度':<15}")
print("-"*80)
for i, (city, date_str, weather, temp, crawl_time) in enumerate(weather_data, 1):
print(f"{i:<4} {city:<8} {date_str:<15} {weather:<30} {temp:<15}")
print("="*80)
def main():
"""主函数"""
# 城市列表
cities = ['北京', '上海', '广州', '深圳', '杭州']
# 创建爬虫
crawler = WeatherCrawler()
print("开始爬取天气数据...")
print("目标网站: 中国气象网 (weather.com.cn)")
print("目标城市:", ", ".join(cities))
print("-" * 50)
all_weather_data = []
# 爬取每个城市的天气数据
for city in cities:
print(f"正在爬取 {city} 的天气数据...")
weather_data = crawler.crawl_weather_data(city)
if weather_data:
# 添加日期描述
for i, data in enumerate(weather_data):
if i == 0:
data['date_desc'] = '今天'
elif i == 1:
data['date_desc'] = '明天'
elif i == 2:
data['date_desc'] = '后天'
else:
data['date_desc'] = f'第{i+1}天'
# 保存到数据库
crawler.save_to_database(weather_data)
all_weather_data.extend(weather_data)
print(f" {city}: 成功获取 {len(weather_data)} 天天气预报")
else:
print(f" {city}: 爬取失败")
# 从数据库查询并显示结果
print("\n从数据库查询天气数据...")
for city in cities:
data = crawler.query_weather_data(city)
if data:
print(f"\n{city}的天气预报:")
crawler.print_weather_table(data)
print(f"\n任务完成!数据已保存到数据库: {crawler.database_name}")
print("Gitee文件夹链接:https://gitee.com/haimisha/2025_creat_project/tree/master/%E4%BD%9C%E4%B8%9A%E4%BA%8C/ppt%E5%AE%9E%E9%AA%8C/%E5%AE%9E%E9%AA%8C2.1")
if name == "main":
main()
`
1.2 结果





2)心得体会
一开始我总是提取不到正确的天气描述或温度,后来发现是我选错了 HTML 标签或者 class 名称。通过反复查看网页源码,我才慢慢找到了正确的元素路径。我用了 SQLite 数据库,把每一天的天气存为一条记录。刚开始不太熟悉 SQL 语句,比如 INSERT INTO怎么写、字段名是否匹配等,后来通过查资料和模仿示例,慢慢搞定了。我最大的感受是:看似简单的功能,背后也需要耐心和细致的操作,尤其是定位数据和处理异常的时候。另外,我也意识到,数据库虽然看起来“高大上”,但用 Python 操作起来其实也挺简单,是存储数据的好帮手。
作业二
1)题目:要求用requests和BeautifulSoup库方法定向爬取股票相关信息,并存储在数据库中。
候选网站:东方财富网:https://www.eastmoney.com/
新浪股票:http://finance.sina.com.cn/stock/
技巧:在谷歌浏览器中进入F12调试模式进行抓包,查找股票列表加载使用的url,并分析api返回的值,并根据所要求的参数可适当更改api的请求参数。根据URL可观察请求的参数f1、f2可获取不同的数值,根据情况可删减请求的参数。
参考链接:https://zhuanlan.zhihu.com/p/50099084
输出信息:
Gitee文件夹链接

1.1 代码
`
def get_total_stock_count() -> Tuple[int, int]:
"""
获取股票总数量和每页数据量,用于计算总页数
返回: (总数据量, 每页数据量)
"""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'http://quote.eastmoney.com/center/gridlist.html',
'Accept': 'application/json, text/javascript, /; q=0.01'
}
url = "http://82.push2.eastmoney.com/api/qt/clist/get"
# 先获取第一页数据,从中提取总数据量
params = {
'pn': '1',
'pz': '20', # 先设置一个较小的值,快速获取总数量
'po': '1',
'np': '1',
'fltt': '2',
'invt': '2',
'fid': 'f3',
'fs': 'm:0 t:6,m:0 t:80,m:1 t:2,m:1 t:23',
'fields': 'f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f12,f13,f14,f15,f16,f17,f18,f20,f21,f23,f24,f25,f22,f11,f62,f128,f136,f115,f152',
'_': str(int(time.time() * 1000))
}
try:
response = requests.get(url, headers=headers, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if data['data'] is None:
print("无法获取股票数据总数")
return 0, 20
total = data['data']['total'] # 总数据量
# 实际每页数据量可能小于请求的pz,这里我们使用实际返回的数据量
actual_size = len(data['data']['diff']) if data['data']['diff'] else 20
print(f"检测到总共有 {total} 只股票,每页显示 {actual_size} 只")
return total, actual_size
except Exception as e:
print(f"获取总数据量失败: {e}")
return 0, 20 # 默认值
def get_all_pages_stock_data(max_pages=1000, delay=1):
"""
爬取全部页面的股票数据
Args:
max_pages: 最大爬取页数限制,防止意外情况
delay: 请求间隔时间(秒),避免被封IP
"""
# 先获取总数据量和每页数据量
total_count, page_size = get_total_stock_count()
if total_count == 0:
print("无法获取股票数据总数,将使用默认设置爬取前50页")
total_pages = min(50, max_pages)
page_size = 20
else:
# 计算总页数: 总数据量/每页数据量,向上取整
total_pages = (total_count + page_size - 1) // page_size
total_pages = min(total_pages, max_pages) # 不超过最大限制
print(f"开始爬取全部 {total_pages} 页数据,预计耗时约 {total_pages * delay} 秒...")
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'http://quote.eastmoney.com/center/gridlist.html',
'Accept': 'application/json, text/javascript, */*; q=0.01'
}
url = "http://82.push2.eastmoney.com/api/qt/clist/get"
all_stocks = []
current_index = 1
for page in range(1, total_pages + 1):
print(f"正在爬取第 {page}/{total_pages} 页...", end=" ")
params = {
'pn': str(page),
'pz': str(page_size),
'po': '1',
'np': '1',
'fltt': '2',
'invt': '2',
'fid': 'f3',
'fs': 'm:0 t:6,m:0 t:80,m:1 t:2,m:1 t:23',
'fields': 'f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f12,f13,f14,f15,f16,f17,f18,f20,f21,f23,f24,f25,f22,f11,f62,f128,f136,f115,f152',
'_': str(int(time.time() * 1000))
}
try:
response = requests.get(url, headers=headers, params=params, timeout=15)
response.raise_for_status()
data = response.json()
if data['data'] is None or not data['data']['diff']:
print(f"第 {page} 页没有数据,可能已到达末尾")
break
stock_data = data['data']['diff']
# 解析当前页数据
page_stocks = []
for stock in stock_data:
stock_info = {
'序号': current_index,
'代码': stock.get('f12', ''),
'名称': stock.get('f14', ''),
'最新价': round(stock.get('f2', 0), 2),
'涨跌幅': f"{stock.get('f3', 0)}%",
'涨跌额': round(stock.get('f4', 0), 2),
'成交量': format_volume(stock.get('f5', 0)),
'成交额': format_amount(stock.get('f6', 0)),
'振幅': f"{stock.get('f7', 0)}%",
'换手率': f"{stock.get('f8', 0)}%" if stock.get('f8') else "0%",
'页码': page
}
page_stocks.append(stock_info)
current_index += 1
all_stocks.extend(page_stocks)
print(f"成功获取 {len(page_stocks)} 条数据")
# 添加延时,避免请求过于频繁
if page < total_pages: # 最后一页不需要延时
time.sleep(delay)
except requests.exceptions.Timeout:
print(f"第 {page} 页请求超时,跳过...")
continue
except requests.exceptions.RequestException as e:
print(f"第 {page} 页网络错误: {e}")
# 遇到网络错误时等待更长时间
time.sleep(delay * 3)
continue
except Exception as e:
print(f"第 {page} 页处理错误: {e}")
continue
return all_stocks
def format_volume(volume: int) -> str:
"""格式化成交量"""
if volume >= 100000000:
return f"{volume/100000000:.2f}亿手"
elif volume >= 10000:
return f"{volume/10000:.2f}万手"
else:
return f"{volume}手"
def format_amount(amount: float) -> str:
"""格式化成交额"""
if amount >= 100000000:
return f"{amount/100000000:.2f}亿元"
elif amount >= 10000:
return f"{amount/10000:.2f}万元"
else:
return f"{amount:.2f}元"
def print_stock_summary(stocks: List[Dict]):
"""打印数据摘要"""
if not stocks:
print("没有获取到股票数据")
return
print(f"\n爬取完成!共获取 {len(stocks)} 只股票的数据")
# 按页码统计
pages = set(stock['页码'] for stock in stocks)
print(f"成功爬取 {len(pages)} 页数据")
# 显示前5只股票作为示例
print("\n前5只股票数据示例:")
headers = ['序号', '代码', '名称', '最新价', '涨跌幅', '涨跌额', '成交量', '成交额']
header_format = "{:<4} {:<8} {:<10} {:<8} {:<8} {:<8} {:<12} {:<12}"
print(header_format.format(*headers))
print("-" * 80)
for i, stock in enumerate(stocks[:5]):
row_format = "{:<4} {:<8} {:<10} {:<8} {:<8} {:<8} {:<12} {:<12}"
print(row_format.format(
stock['序号'],
stock['代码'],
stock['名称'][:8],
stock['最新价'],
stock['涨跌幅'],
stock['涨跌额'],
stock['成交量'],
stock['成交额']
))
def save_to_csv(stocks: List[Dict], filename: str = "all_stocks_data.csv"):
"""保存数据到CSV文件"""
if not stocks:
print("没有数据可保存")
return
df = pd.DataFrame(stocks)
df.to_csv(filename, index=False, encoding='utf-8-sig')
print(f"\n全部数据已保存到 {filename}")
def main():
"""主函数"""
print("开始爬取全部页面的股票数据...")
print("=" * 60)
start_time = time.time()
# 爬取全部页面数据
# 设置最大页数限制为200页,延时2秒
stocks = get_all_pages_stock_data(max_pages=200, delay=2)
end_time = time.time()
# 打印摘要信息
print_stock_summary(stocks)
# 保存到CSV
save_to_csv(stocks)
# 显示耗时
elapsed = end_time - start_time
print(f"\n总耗时: {elapsed:.2f} 秒")
print(f"平均每页耗时: {elapsed/len(stocks)*20 if stocks else 0:.2f} 秒")
print("Gitee文件夹链接:https://gitee.com/haimisha/2025_creat_project/tree/master/%E4%BD%9C%E4%B8%9A%E4%BA%8C/ppt%E5%AE%9E%E9%AA%8C/%E5%AE%9E%E9%AA%8C2.2")
if name == "main":
main()`
1.2 结果

2)心得体会
这个作业相比第一个难度有所提升,因为爬取的是股票信息,这网站通常数据量大、更新快,而且很多数据是通过 JavaScript 动态加载的,并不会直接出现在 HTML 源码里。一开始我一直在分析网页的 HTML,想直接从页面上提取数据,但怎么都找不到关键信息。后来经同学提醒,才想到去“Network”里找接口,才发现数据是通过 API 返回的 JSON。API 请求中有很多参数,比如 f1、f2、ut、page 等,有些是控制返回哪些字段的。我一开始不太懂这些参数含义,只能照着参考文章里的示例去设置,后来慢慢理解了一些。JSON 返回的数据结构有点复杂,比如最新价、涨跌幅等可能嵌套在某个对象里,我得仔细阅读返回结果,才能正确提取字段。通过这次实践,我不仅学会了如何找到并调用数据接口,还提升了使用 Python 处理 JSON 数据的能力,同时也加深了对数据库字段设计和数据存储流程的理解。
作业三
1)题目:要求爬取中国大学2021主榜(https://www.shanghairanking.cn/rankings/bcur/2021)所有院校信息,并存储在数据库中,同时将浏览器F12调试分析的过程录制Gif加入至博客中。
技巧:分析该网站的发包情况,分析获取数据的api
输出信息:
Gitee文件夹链接
排名 学校 省市 类型 总分
1 清华大学 北京 综合 969.2
1.1 代码
`import requests
from bs4 import BeautifulSoup
import sqlite3
import re
目标网页 URL
url = "https://www.shanghairanking.cn/rankings/bcur/2021"
请求头,模拟浏览器
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0 Safari/537.36"
}
发送 GET 请求
print("正在获取网页数据...")
response = requests.get(url, headers=headers)
response.encoding ='utf-8'
if response.status_code != 200:
print(f"网页访问失败,状态码:{response.status_code}")
else:
print("网页访问成功,正在解析数据...")
# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(response.text, 'html.parser')
# 找到排名表格 —— 根据实际网页结构来定位,可能需要调整
table = soup.find('table', {'class': 'rk-table'})
if not table:
table = soup.find('table') #找第一个表格
universities = []
if table:
# 遍历表格行,跳过表头
rows = table.find_all('tr')[1:] # 从第二行开始,第一行是标题
for idx, row in enumerate(rows, start=1): # 从1开始计数,对应排名
cols = row.find_all('td')
if len(cols) >= 6: # 确保至少有6列数据
rank = cols[0].get_text(strip=True)
# 优先尝试获取带中文的名称(通常在第一个 <div> 或直接 td 文本)
name_elem = cols[1].find('div', recursive=False) or cols[1]
name_full = name_elem.get_text(strip=True).split('\n')[0].strip()
# 尝试提取中文名(假设中文名在前、英文在后,用空格分隔)
name_parts = re.split(r'\s+', name_full, maxsplit=1)
name = name_parts[0] if name_parts else name_full
label = cols[1].get_text(strip=True) # 双一流/985/211
location = cols[2].get_text(strip=True) # 所在地
category = cols[3].get_text(strip=True) # 学校类型(综合/理工等)
score = cols[4].get_text(strip=True) # 总分
# 洗数据,确保排名是数字
rank = int(rank) if rank.isdigit() else None
universities.append({
'排名': rank,
'学校名称': name,
'标签': label,
'所在地': location,
'学校类型': category,
'总分': score
})
# 打印当前行的关键信息,模拟示例图输出
print(f"{rank:<5}{name:<15}{location:<8}{category:<8}{score:<10}")
print(f"共爬取到 {len(universities)} 条大学数据。")
# 3. 存储到 SQLite 数据库
if universities:
print("正在保存数据到数据库...")
conn = sqlite3.connect('university_ranking_2021.db') # 创建或打开数据库
cursor = conn.cursor()
# 创建表(如果不存在)
cursor.execute('''
CREATE TABLE IF NOT EXISTS universities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
排名 INTEGER,
学校名称 TEXT,
标签 TEXT,
所在地 TEXT,
学校类型 TEXT,
总分 TEXT
)
''')
# 插入数据
for uni in universities:
cursor.execute('''
INSERT INTO universities (排名, 学校名称, 标签, 所在地, 学校类型, 总分)
VALUES (?, ?, ?, ?, ?, ?)
''', (uni['排名'], uni['学校名称'], uni['标签'], uni['所在地'], uni['学校类型'], uni['总分']))
conn.commit()
conn.close()
print("数据已成功保存到数据库:university_ranking_2021.db")
else:
print("未能提取到大学数据,请检查网页结构是否发生变化。")
print("Gitee代码保存路径:https://gitee.com/haimisha/2025_creat_project/tree/master/%E4%BD%9C%E4%B8%9A%E4%BA%8C/ppt%E5%AE%9E%E9%AA%8C/%E5%AE%9E%E9%AA%8C2.3")
`
1.2 结果
1.2.1 代码运行结果

1.2.2 f12调试
屏幕录制 2025-11-09 200208.gif
2)心得体会:
这个作业是三个任务里综合难度最高的一个,因为它不仅要求我们爬取“中国大学2021主榜”的排名信息,还要求我们把 F12 分析过程录制成 GIF,并撰写一篇结构清晰、带代码和图表的博客。我打开了 F12 开发者工具,在 Network 面板中找到了返回大学排名数据的那个 JSON 格式的 API 接口。通过分析这个 API,我直接请求 JSON 数据,获取了排名、学校、省市、类型、总分等字段,然后存入数据库。:一开始我试图直接从网页 HTML 中找排名表格,但发现数据是动态加载的,HTML 里根本找不到具体排名。后来通过抓包才发现了真正的 API。

浙公网安备 33010602011771号