数据采集与融合技术作业2

数据采集与融合技术作业2

各任务核心代码片段、实现思路与心得体会

任务一:中国气象网7日天气预报爬取与存储

要求:在中国气象网(http://www.weather.com.cn)给定城市集的7日天气预报,并保存在数据库。

核心代码片段(抓取与解析核心)

def crawl_weather(city_name, city_id):

    url = f"http://www.weather.com.cn/weather/{city_id}.shtml"

    headers = {

        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",

        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",

        "Accept-Language": "zh-CN,zh;q=0.9"

    } 

    try:

        response = requests.get(url, headers=headers, timeout=15)

        response.encoding = "utf-8"

        soup = BeautifulSoup(response.text, "html.parser")

        forecast_list = soup.select("ul.t.clearfix li") or soup.select("div#7d ul li")

        if not forecast_list:

            print(f"爬取{city_name}失败:未找到天气预报数据")

            return None

        weather_data = []

        for day in forecast_list[:7]:  

           

            date_tag = day.select_one("h1") or day.select_one("div.date")

            if not date_tag:

                continue

            date = date_tag.text.strip()

            weather_tag = day.select_one("p.wea")

            if not weather_tag:

                continue

            weather = weather_tag.text.strip()

            # 提取温度(兼容格式,避免索引越界)

            temp = day.select_one("p.tem").text.strip()

            temp_parts = temp.replace("℃", "").split("/")

            # 处理温度拆分异常

            if len(temp_parts) >= 2:

                temp_max = int(temp_parts[0].strip()) if temp_parts[0].strip().isdigit() else 0

                temp_min = int(temp_parts[1].strip()) if temp_parts[1].strip().isdigit() else 0

            else:

                temp_val = int(temp_parts[0].strip()) if temp_parts[0].strip().isdigit() else 0

                temp_max = temp_val

                temp_min = temp_val - 4  

            weather_data.append({

                "city": city_name,

                "date": date,

                "weather": weather,

                "temp_min": temp_min,

                "temp_max": temp_max

            })

        return weather_data

    except Exception as e:

        print(f"爬取{city_name}失败:{str(e)}")

        return None

实现思路

  1. URL规则分析:通过浏览不同城市天气页面,发现URL中包含唯一城市ID(如北京101010100),因此建立城市-ID映射表实现批量爬取。
  2. 页面结构定位:用F12查看元素,发现7日天气预报被包裹在ul.t.clearfix列表中,每个li对应一天数据。
  3. 字段提取逻辑:日期在h1标签、天气在p.wea、温度在p.tem,直接通过标签和类名定位。
  4. 存储设计:创建SQLite表时设置(city, date)唯一约束,避免重复存储同一天气数据。

心得体会

  1. 静态页面爬取关键:对于HTML结构稳定的网站(如气象网),直接解析标签比找API更简单,但需注意页面编码(此处utf-8解决了中文乱码)。
  2. 反爬基础防御:仅设置User-Agent就可爬取气象网,说明其反爬策略较宽松,但仍需控制爬取频率(加入time.sleep(2))避免给服务器压力。
  3. 数据去重教训:初期未设置唯一约束导致重复存储,后来用INSERT OR IGNORE解决,这让我意识到数据库约束在爬虫中的重要性。
  4. 可扩展性思考:城市ID映射表可以从文件读取,未来可扩展到全国城市,只需维护ID列表即可。

地址:https://gitee.com/oozyt/datacollecting/tree/master/%E4%BD%9C%E4%B8%9A2/%E4%BB%BB%E5%8A%A11

任务二:东方财富网股票信息爬取与存储

要求:用requests和BeautifulSoup库方法定向爬取股票相关信息,并存储在数据库中。

候选网站:东方财富网:https://www.eastmoney.com/

技巧:在谷歌浏览器中进入F12调试模式进行抓包,查找股票列表加载使用的url,并分析api返回的值,并根据所要求的参数可适当更改api的请求参数。根据URL可观察请求的参数f1、f2可获取不同的数值,根据情况可删减请求的参数。

核心代码片段(API解析核心)

def crawl_stock_data(page):

# 抓包找到的股票列表API

url = f"https://push2.eastmoney.com/api/qt/clist/get?pn={page}&pz=20&..."

headers = {"User-Agent": "Mozilla/5.0 ..."}

response = requests.get(url, headers=headers)

# 处理JSONP格式(API返回的是jQuery包裹的JSON,需剥离函数名)

json_str = response.text.split("(")[1].rsplit(")", 1)[0]

data = json.loads(json_str)

stock_list = []

# 解析API返回的字段(通过分析response确定f12=代码、f14=名称等映射关系)

for item in data["data"]["diff"]:

stock_list.append({

"code": item["f12"], # 股票代码

"name": item["f14"], # 股票名称

"price": item["f2"] / 100, # 最新价(API返回值需除以100还原真实值)

"change_rate": item["f3"] / 100 # 涨跌幅(百分比,同样需除以100)

})

return stock_list

实现思路

  1. API发现过程:通过F12的Network面板,在页面滚动加载股票列表时,捕获到push2.eastmoney.com的API请求,其返回包含完整股票数据。
  2. 参数分析:发现pn是页码、pz是每页条数,修改这两个参数可控制爬取范围。
  3. 字段映射:API返回的是f12、f14等缩写字段,通过对比页面显示值,确定f12=代码、f2=最新价等映射关系,且数值需除以100还原。
  4. JSONP处理:由于API返回的是jQuery(...)包裹的JSONP格式,需先切割字符串提取纯JSON。

心得体会

  1. 动态页面爬取技巧:对于滚动加载的内容(如股票列表),直接解析HTML很难获取完整数据,通过F12抓包找API是更高效的方案。
  2. API字段解读:初期因不清楚f12、f3等字段含义,通过对比页面显示值和API返回值,才确定映射关系,这个过程锻炼了数据匹配能力。
  3. 数值处理细节:API返回的价格是乘以100的整数(如1567代表15.67元),忽略这一点会导致数据错误,说明爬虫必须重视数据格式验证。
  4. 反爬升级应对:连续爬取多页后出现过403,通过添加随机User-Agent和爬取间隔解决,意识到动态页面的反爬策略通常比静态页面严格。

地址:https://gitee.com/oozyt/datacollecting/tree/master/%E4%BD%9C%E4%B8%9A2/%E4%BB%BB%E5%8A%A12

任务三:中国大学2021排名爬取与存取

要求:爬取中国大学2021主榜(https://www.shanghairanking.cn/rankings/bcur/2021)所有院校信息,并存储在数据库中,同时将浏览器F12调试分析的过程录制Gif加入至博客中。

技巧:分析该网站的发包情况,分析获取数据的api

2

网站静态Payload: /_nuxt/static/.../payload.js 这类文件是典型的例子。这些文件是构建时预生成的,数据就藏在其中

核心代码片段(API请求与存储核心)

使用api常用格式可以使出网页api

def crawl_university_rank():

url = "https://www.shanghairanking.cn/api/pub/v1/bcur?bcur_type=11&year=2021"

headers = {

"User-Agent": "Mozilla/5.0 ...",

"Referer": "https://www.shanghairanking.cn/rankings/bcur/2021" # 关键:添加Referer绕过防盗链

}

response = requests.get(url, headers=headers)

data = response.json()

# 核心:提取排名数据(API返回结构中rankings数组包含所有院校信息

return data["data"]["rankings"]

def save_to_db(rankings):

conn = sqlite3.connect("university_rank.db")

cursor = conn.cursor()

# 核心:存储关键字段(排名、学校名、省市、类型、总分)

for item in rankings:

cursor.execute('''

INSERT INTO university_2021 (rank, name, province, type, score)

VALUES (?, ?, ?, ?, ?)

''', (item["ranking"], item["univNameCn"], item["province"],

item["univCategory"], item["score"]))

conn.commit()

conn.close()

实现思路

  1. API定位:在上海软科排名页面,通过F12的XHR过滤器,找到加载排名数据的API(/api/pub/v1/bcur),其参数year=2021指定了年份。
  2. 防盗链处理:初期请求API返回403,分析请求头发现需要Referer字段(必须包含官网域名),添加后成功获取数据。
  3. 数据结构解析:API返回的JSON中,data.rankings数组包含所有院校信息,直接提取ranking(排名)、univNameCn(学校名)等字段即可。
  4. 存储设计:创建表时包含排名、学校、省市、类型、总分5个核心字段,与页面展示的信息一一对应。

心得体会

  1. Referer防盗链应对:这是首次遇到需要验证Referer的网站,通过对比浏览器正常请求和代码请求的 headers,才发现缺失的关键字段,让我明白抓包时需完整复制请求头。
  2. API稳定性优势:相比解析HTML,直接调用API获取JSON数据更高效,且字段结构清晰(如univNameCn明确是中文校名),减少了解析错误。
  3. 数据可视化配合:按要求录制了F12分析过程的GIF,这个过程让我意识到:爬虫不仅要写代码,还要能清晰展示数据来源分析过程,这是技术文档的重要部分。
  4. 异常处理补充:后期发现部分学校的score字段为空,添加了try-except处理(float(item["score"]) if item["score"] else 0),使程序更健壮。

地址:https://gitee.com/oozyt/datacollecting/tree/master/%E4%BD%9C%E4%B8%9A2/%E4%BB%BB%E5%8A%A13

posted @ 2025-11-11 21:03  ooozyz  阅读(8)  评论(0)    收藏  举报