102302148_谢文杰_第二次作业
第一题
要求
在中国气象网(http://www.weather.com.cn)给定城市集的7日天气预报,并保存在数据库。
代码
点击查看代码
import requests
from bs4 import BeautifulSoup
import pandas as pd
import sqlite3
import re
from beautifultable import BeautifulTable
def get_weather_data(url):
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'
}
try:
res = requests.get(url,headers=headers)
res.encoding='utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
lis = soup.select("ul[class='t clearfix'] li")
table = BeautifulTable()
table.set_style(BeautifulTable.STYLE_COMPACT)
table.columns.header = ["城市", "日期", "天气", "温度"]
table.columns.alignment = BeautifulTable.ALIGN_CENTER
db = WeatherDB()
db.openDB()
for li in lis:
try:
date = li.select('h1')[0].text
weather = li.select('p[class="wea"]')[0].text
# # 处理温度可能的结构差异(避免索引错误)
temp_span = li.select('p[class="tem"] span')
temp_i = li.select('p[class="tem"] i')[0].text
temp = temp_span[0].text+'/'+temp_i if temp_span else temp_i
table.rows.append(["北京",date,weather,temp])
db.insert('北京', date, weather, temp)
print(f"✓ 成功插入数据: 北京 - {date} - {weather} - {temp}")
except Exception as err:
print(err)
print(table)
print()
except Exception as e:
print(f"获取天气数据失败: {e}")
db.closeDB()
print(f"✅ 数据已成功存储到数据库 weathers.db")
class WeatherDB:
def openDB(self):
self.con = sqlite3.connect("weathers.db")
self.cursor = self.con.cursor()
try:
self.cursor.execute(
"create table weathers (wCity varchar(16),wDate varchar(16),wWeather varchar(64),wTemp varchar(32),constraint pk_weather primary key (wCity,wDate))")
except:
self.cursor.execute("delete from weathers")
def closeDB(self):
self.con.commit()
self.con.close()
def insert(self, city, date, weather, temp):
try:
self.cursor.execute("insert into weathers (wCity,wDate,wWeather,wTemp) values (?,?,?,?)",
(city, date, weather, temp))
except Exception as err:
print(err)
url="https://www.weather.com.cn/weather/101010100.shtml"
get_weather_data(url)
实验结果

心得体会
这次实验种学会了,使用模拟浏览器User-Agent的方法来发送请求,稳定性更强;同时在使用BeautifulSoup解析时,对于温度标签的处理要更加留意,有些结构不一致,就要正确使用if/else的方法分开讨论,同时可以加入一些调试信息和输出,可以更加直观查看运行结果。
第二题
要求
用requests和BeautifulSoup库方法定向爬取股票相关信息,并存储在数据库中。
核心代码
- 相对于在普通的HTML里面进行爬取,更应该去分析Network面板中的XHR请求。爬取API返回的JSON数据,要更加稳定,数据更加完整,关键在于正确找到js文件,然后对其进行处理。
![image]()
- 在发送请求时,我们仅仅使用模拟浏览器User-Agent的方法来发送请求,仍然会有被屏蔽的风险,所以,可以再加上cookies,相当于你在东方财富网的"身份证",服务器通过它们来:识别你是合法用户而非恶意爬虫,维持你的登录状态和会话。
- 对于我们使用的URL,要明白其中参数的含义,不然容易出现一些问题,比如fid是按照某个参数进行排序,之前没有留意,总是出问题。
def getHtml(cmd, page):
# 构建完整的URL
url = f"https://push2.eastmoney.com/api/qt/clist/get?fid=f3&po=1&pz=20&pn={page}&fs={cmd}&fields=f12,f13,f14,f1,f2,f4,f3,f152,f5,f6,f7,f15,f18,f16,f17,f10,f8,f9,f23"
# 简化headers,移除可能包含中文的字段
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'application/json, text/plain, */*',
'Connection': 'keep-alive',
'Host': 'push2.eastmoney.com',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
}
# 简化cookies
cookies = {
'qgqp_b_id': '17a61503fd887741a519997ccdd24033',
'st_pvi': '46223553544940',
'st_si': '70041422244381',
}
try:
r = requests.get(url, headers=headers, cookies=cookies, timeout=10)
r.raise_for_status()
data = r.json()
return data
except Exception as e:
print(f"请求失败: {e}")
return None
- 对于爬取出来的数据,数字正确,形式未必正确,因此我们对数据进行格式处理,进行数据计算和单位转换
def process_data_stock(data):
stocks = []
for stock in data:
stocks.append([
stock.get("f12", ""), # 0-代码
stock.get("f14", ""), # 1-名称
f"{stock.get('f2', 0) / 100:.2f}" if stock.get("f2") else "-", # 2-最新价
f"{stock.get('f3', 0) / 100:.2f}%" if stock.get("f3") else "-", # 3-涨跌幅
f"{stock.get('f4', 0) / 100:.2f}" if stock.get("f4") else "-", # 4-涨跌额
str(stock.get("f5", "")), # 5-成交量
f"{int(stock.get('f6', 0)) / 1000000:.1f}万" if stock.get("f6") else "-", # 6-成交额
f"{stock.get('f7', 0) / 100:.2f}%" if stock.get("f7") else "-" # 7-涨幅
])
return stocks
5.对于输出结果想要给它整齐输出,可以使用beautifultable函数进行规范整齐输出。
def show_stocks(stocks, plate_name):
"""使用BeautifulTable显示股票数据"""
if not stocks:
return
print(f"\n🎯 【{plate_name}】股票列表(共{len(stocks)}只)")
print("=" * 120)
table = BeautifulTable()
table.set_style(BeautifulTable.STYLE_COMPACT)
table.columns.header = ["序号", "代码", "名称", "最新价", "涨跌幅", "涨跌额", "成交量", "成交额", "振幅"]
table.columns.alignment = BeautifulTable.ALIGN_CENTER
table.columns.width = [6, 8, 12, 8, 8, 12, 12, 12, 12]
data = process_data_stock(stocks)
for i, stock in enumerate(data, 1):
table.rows.append([i, stock[0], stock[1], stock[2], stock[3], stock[4], stock[5],stock[6], stock[7]])
print(table)
print()
def save_to_excel(stocks, plate):
if not stocks:
print(f"❌ 【{plate}】无数据可保存")
return
data = process_data_stock(stocks)
# 创建DataFrame
df = pd.DataFrame(data, columns=[
"代码", "名称", "最新价", "涨跌幅", "涨跌额",
"成交量", "成交额", "涨幅"
])
# 保存为Excel文件
filename = f"stock_data/{plate}.xlsx"
try:
df.to_excel(filename, index=False)
print(f"💾 【{plate}】数据已保存到: {filename}")
return filename
except Exception as e:
print(f"❌ 【{plate}】保存Excel失败: {e}")
return None
部分运行结果


心得体会
通过这次实验,我学会了通过URL参数精确控制数据的获取范围。URL字符串中,每一个参数都有其特定含义:比如:pn控制分页,fs指定市场类型,fields定义需要返回的字段。这种参数化的请求方式让我能够精准地获取所需数据,避免了传统爬取中"获取全部再过滤"的资源浪费。特别是在分页处理上,通过简单的页面参数递增,就实现了大规模数据的批量获取,同时直接获取结构化的JSON数据,跳过了繁琐的HTML解析环节。这不仅大大提高了代码的执行效率,还显著提升了程序的稳定性。但是需要花时间找到真正的数据接口。
第三题
要求
爬取中国大学2021主榜(https://www.shanghairanking.cn/rankings/bcur/2021) 所有院校信息,并存储在数据库中,同时将浏览器F12调试分析的过程录制Gif加入至博客中。
核心代码
- 先使用正则表达式专门识别和解析Nuxt.js框架的JSONP数据格式,然后对JSONP函数调用的问题,创建独立的JavaScript执行环境,执行函数,获取数据。
def extract_bcur_ranking_data(url: str) -> List[Dict]:
try:
# 发送请求获取数据
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'
}
response = requests.get(url, headers=headers)
response.encoding = 'utf-8'
if response.status_code != 200:
print(f"请求失败,状态码: {response.status_code}")
return []
content = response.text
# 提取整个JSONP函数调用
pattern = r'__NUXT_JSONP__\("/rankings/bcur/\d+",\s*\(function\(([^)]*)\)\s*{([\s\S]*)}\s*\(([^)]*)\)\)\);'
match = re.search(pattern, content)
if not match:
print("未找到JSON数据")
return []
# 获取函数参数、函数体和实际参数
func_params = match.group(1) # 函数参数: a, b, c, d, e, f, ...
function_body = match.group(2) # 函数体
actual_params = match.group(3) # 实际参数值
# 使用js2py执行JavaScript代码
return extract_with_js_engine(func_params, function_body, actual_params)
except Exception as e:
print(f"提取数据时出错: {e}")
return []
def extract_with_js_engine(func_params: str, function_body: str, actual_params: str) -> List[Dict]:
"""
使用JavaScript引擎执行函数并提取数据
"""
try:
# 创建JavaScript执行环境
context = js2py.EvalJs()
# 构建完整的JavaScript代码
js_code = f"""
// 定义函数
function getData({func_params}) {{
{function_body}
}}
// 调用函数并返回结果
var result = getData({actual_params});
// 返回univData数组
result.data[0].univData;
"""
# 执行JavaScript代码
univ_data = context.eval(js_code)
# 转换为Python列表
universities = []
for univ in univ_data:
university_info = {
'排名': univ.get('ranking', ''),
'学校名称': univ.get('univNameCn', ''),
'英文名称': univ.get('univNameEn', ''),
'学校类型': univ.get('univCategory', ''),
'所在省份': univ.get('province', ''),
'总分': univ.get('score', '')
}
universities.append(university_info)
return universities
except Exception as e:
print(f"JavaScript执行失败: {e}")
return []
部分运行结果

找到正确的API接口(gif)

心得体会
在完成这个大学排名数据爬虫项目的过程中,我经历了一次技术认知的重要升级,对现代Web数据获取有了全新的理解和体会。相对于JSON文件里面,所有的数据都是存在的,而JSONP函数,需要对其进行调用,然后获取数据,这是一个不同的地方,最开始想直接使用正则表达式或者beautifulsoup来实现,发现都很难实现,在网上学习到了这种方法。


浙公网安备 33010602011771号