zyxsyx

导航

数据采集作业2

作业1
在中国气象网(http://www.weather.com.cn)给定城市集的7日天气预报,并保存在数据库。
代码和结果
打开网站查看每一个城市对应的代码,比如图片中武汉的代码为101200101,选取4个城市的代码用于编写我们的爬虫
image
代码:

点击查看代码
import requests
from bs4 import BeautifulSoup
import sqlite3
from datetime import datetime
import time

# 目标城市配置(北京、杭州、武汉、重庆)
CITY_INFO = [
    {"name": "北京", "code": "101010100"},
    {"name": "杭州", "code": "101210101"},
    {"name": "武汉", "code": "101200101"},
    {"name": "重庆", "code": "101040100"}
]

# 请求头
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Referer": "http://www.weather.com.cn/"
}

# 初始化数据库
def init_db():
    conn = sqlite3.connect("weather_db.db")
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS weather_data (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            area TEXT NOT NULL,
            date TEXT NOT NULL,
            weather_info TEXT NOT NULL,
            temperature TEXT NOT NULL,
            crawl_time DATETIME NOT NULL,
            UNIQUE(area, date)
        )
    ''')
    conn.commit()
    conn.close()

# 爬取单个城市的天气数据并存储到数据库
def crawl_and_save(city_name, city_code):
    url = f"http://www.weather.com.cn/weather/{city_code}.shtml"
    try:
        time.sleep(3)
        response = requests.get(url, headers=HEADERS, timeout=15)
        response.encoding = "utf-8"
        
        soup = BeautifulSoup(response.text, "html.parser")
        forecast_ul = soup.find("ul", class_="t clearfix")
        
        if not forecast_ul:
            print(f"[{city_name}] 未找到天气数据(页面结构可能变化)")
            return
        
        crawl_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        conn = sqlite3.connect("weather_db.db")
        cursor = conn.cursor()
        
        for index, li in enumerate(forecast_ul.find_all("li")[:7], 1):
            date = li.find("h1").text.strip()
            weather = li.find("p", class_="wea").text.strip()
            temp = li.find("p", class_="tem").text.strip().replace("\n", "")
            wind = li.find("p", class_="win").text.strip().replace("\n", "")
            weather_info = f"{weather},{wind}" if wind else weather
            
            cursor.execute('''
                INSERT OR IGNORE INTO weather_data 
                (area, date, weather_info, temperature, crawl_time)
                VALUES (?, ?, ?, ?, ?)
            ''', (city_name, date, weather_info, temp, crawl_time))
            
            print(f"序号:{index},地区:{city_name},日期:{date},天气信息:{weather_info},温度:{temp}")
        
        conn.commit()
        conn.close()
        print(f"[{city_name}] 数据已保存至数据库\n")
    
    except Exception as e:
        print(f"[{city_name}] 爬取失败:{str(e)}\n")

# 主函数
if __name__ == "__main__":
    init_db()
    print("开始爬取4个城市的天气数据...\n")
    for city in CITY_INFO:
        crawl_and_save(city["name"], city["code"])
    print("所有城市爬取结束,数据已保存至weather_db.db")
结果:

image

实验心得
这个实验我们通过预先配置目标城市名称及对应的天气网城市编码,以模拟真实浏览器的请求头向中国天气网发送 HTTP 请求,然后查看一下页面的各个信息都存在什么标签底下,然后用BeautifulSoup 解析 HTML 页面,定位一下存储预报数据的容器标签,提取日期、天气状况、温度等字段,获取 4 个城市的 7 天天气预报网页内容,让我明白了爬取页面时要注意不同url的变化

作业2
用requests和json解析方法定向爬取股票相关信息,并存储在数据库中。
代码和结果
代码:

点击查看代码
import requests
import time
from math import ceil
import pandas as pd

def get_single_page_data(page_num, page_size=50):
    try:
        # 1. 用params参数传递所有请求参数
        url = "https://push2.eastmoney.com/api/qt/clist/get"
        params = {
            "np": 1,
            "fltt": 1,
            "invt": 2,
            "fs": "m:0+t:6+f:!2,m:0+t:80+f:!2,m:1+t:2+f:!2,m:1+t:23+f:!2,m:0+t:81+s:262144+f:!2",
            "fields": "f12,f14,f2,f3,f4,f5,f6,f23",  # 保留需要的字段
            "fid": "f3",
            "pn": page_num, 
            "pz": page_size,
            "po": 1,
            "dect": 1,
            "ut": "fa5fd1943c7b386f172d6893dbfba10b",
            "wbp2u": "|0|0|0|web"
        }
        
        # 2. 模拟浏览器请求,降低反爬概率
        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",
            "Referer": "https://quote.eastmoney.com/"  # 增加来源页
        }
        time.sleep(0.8)  
        response = requests.get(url, params=params, headers=headers, timeout=10)
        response.raise_for_status()  # 请求失败直接抛异常,便于排查
        
        # 3. 解析JSON数据
        stock_json = response.json()
        
        # 4. 校验数据有效性
        if stock_json.get("rc") != 0 or "data" not in stock_json:
            print(f"第{page_num}页数据无效,跳过")
            return None
        return stock_json["data"]["diff"]
    
    except Exception as e:
        print(f"第{page_num}页请求失败:{str(e)},重试1次...")
        # 失败重试1次,提升成功率
        time.sleep(1)
        try:
            response = requests.get(url, params=params, headers=headers, timeout=10)
            response.raise_for_status()
            stock_json = response.json()
            if stock_json.get("rc") == 0 and "data" in stock_json:
                return stock_json["data"]["diff"]
            else:
                print(f"第{page_num}页重试也失败,跳过")
                return None
        except Exception as retry_e:
            print(f"第{page_num}页重试失败:{str(retry_e)},跳过")
            return None

def get_total_stock_count():
    """获取总股票数,用于计算总页数"""
    try:
        url = "https://push2.eastmoney.com/api/qt/clist/get"
        params = {
            "np": 1,
            "fltt": 1,
            "invt": 2,
            "fs": "m:0+t:6+f:!2,m:0+t:80+f:!2,m:1+t:2+f:!2,m:1+t:23+f:!2,m:0+t:81+s:262144+f:!2",
            "fields": "f12",  # 仅需股票代码,减少数据量
            "fid": "f3",
            "pn": 1,
            "pz": 1,  # 每页1条,快速获取总数量
            "ut": "fa5fd1943c7b386f172d6893dbfba10b"
        }
        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"}
        response = requests.get(url, params=params, headers=headers, timeout=10)
        stock_json = response.json()
        
        if stock_json.get("rc") == 0 and "data" in stock_json:
            total = stock_json["data"]["total"]
            print(f" 检测到当前市场共{total}只股票")
            return total
        else:
            print("  无法获取总股票数,默认按5500只处理")
            return 5500
    except Exception as e:
        print(f" 获取总股票数失败:{str(e)},默认按5500只处理")
        return 5500

def crawl_all_stocks_save_excel():
    """全量爬取+去重+仅保存Excel文件"""
    all_stocks = []
    page_size = 50  # 每页50条,效率与稳定性平衡
    total_count = get_total_stock_count()
    total_pages = ceil(total_count / page_size)  # 向上取整,确保爬完所有页
    print(f" 爬取计划:共{total_pages}页,每页{page_size}条\n")
    
    # 1. 循环爬取所有页面
    for page_num in range(1, total_pages + 1):
        print(f" 正在爬取第{page_num}/{total_pages}页...")
        page_data = get_single_page_data(page_num, page_size)
        
        if page_data:
            # 2. 去重:用股票代码(f12,唯一标识)判断,避免重复添加
            new_stock_count = 0
            for stock in page_data:
                stock_code = stock.get("f12")
                # 检查该代码是否已在all_stocks中,不在则添加
                if not any(existing_stock.get("f12") == stock_code for existing_stock in all_stocks):
                    all_stocks.append(stock)
                    new_stock_count += 1
            print(f" 第{page_num}页爬取成功,新增{new_stock_count}只股票,累计{len(all_stocks)}只\n")
        else:
            print(f" 第{page_num}页无有效数据,累计{len(all_stocks)}只股票\n")
    
    # 3. 数据格式化:转换为可读格式(修正原始数值÷100的问题)
    formatted_data = []
    for idx, stock in enumerate(all_stocks, start=1):
        formatted_data.append({
            "序号": idx,
            "股票代码": stock.get("f12", "-"),
            "股票名称": stock.get("f14", "-"),
            "最新价(元)": round(stock.get("f2", 0)/100, 2) if stock.get("f2") != "-" else "-",
            "涨跌幅(%)": round(stock.get("f3", 0)/100, 2) if stock.get("f3") != "-" else 0,
            "涨跌额(元)": round(stock.get("f4", 0)/100, 2) if stock.get("f4") != "-" else "-",
            "成交量(手)": stock.get("f5", "-"),
            "成交额(万元)": round(stock.get("f6", 0)/10000, 1) if stock.get("f6") != "-" else "-",
            "换手率(%)": round(stock.get("f23", 0)/100, 2) if stock.get("f23") != "-" else 0
        })
    
    # 4. 保存为Excel文件
    if formatted_data:
        excel_path = "全量A股股票数据.xlsx"
        # 用pandas生成Excel,列顺序清晰
        df = pd.DataFrame(formatted_data)
        # 调整列顺序,与日常查看习惯一致
        column_order = ["序号", "股票代码", "股票名称", "最新价(元)", "涨跌幅(%)", "涨跌额(元)", "成交量(手)", "成交额(万元)", "换手率(%)"]
        df = df[column_order]
        # 保存Excel(engine=openpyxl支持.xlsx格式)
        df.to_excel(excel_path, index=False, engine="openpyxl")
        print(f" 爬取完成!共获取{len(formatted_data)}只无重复股票数据")
        print(f" Excel文件已保存至:{excel_path}")
    else:
        print(" 未获取到任何有效股票数据,无法生成Excel文件")
    
    return formatted_data

# ------------------- 主程序:直接运行即可 -------------------
if __name__ == "__main__":
    # 启动全量爬取并保存Excel
    crawl_all_stocks_save_excel()
结果:共5000多只股票

image

image

实验心得
image
我们通过f12查看页面网络,然后搜索一下当天股票的代码,就能找到储存股票信息的js文件,我们可以看到股票信息
以及请求的url(有时候爬太多会被反爬)可以在可以爬的时候保存json文件或者用其他url(params参数)进行爬取,(也有尝试过保存json文件的方式)
image
保存json的方式
image
总结:这个任务让我深知F12开发者工具的价值——通过Network面板定位股票API,用requests携带参数请求,解析JSON数据并去重、换算单位,最终保存为Excel,比解析HTML简洁高效太多,核心就是找对接口、理清参数、做好数据清洗。

作业3
爬取中国大学2021主榜(https://www.shanghairanking.cn/rankings/bcur/2021)所有院校信息,并存储在数据库中,同时将浏览器F12调试分析的过程录制Gif加入至博客中。
代码和结果
代码:
抓包寻找url的时候,我们可以看到其实网站的js文件储存了所有的学校信息,而且学校的一些信息是通过变量名——实际值这样的一个映射存在js文件中的,如果我们直接提取js文件,学校的省市什么的只能提取出一些英文字母的变量名,我们在js文件底部能找到映射字典,再通过正则表达式来提取信息的各个字段,保存在数据库里,为了方便直接查看,还保存了一个csv文件

WeChat_20251106115555

点击查看代码
import requests
import re
import sqlite3
import csv

class UniversitySpider:
    def __init__(self):
        # 数据源URL - 包含所有大学排名信息的JS文件
        self.url = "https://www.shanghairanking.cn/_nuxt/static/1762223212/rankings/bcur/2021/payload.js"
    
    def get_js_content(self):
        # 获取原始JS数据
        response = requests.get(self.url)
        return response.text
    
    def build_mapping(self):
        # 构建变量映射表:字母变量 → 实际含义
        mapping = {
            # 学校类型映射
            'e': '理工', 'f': '综合', 'h': '师范', 'm': '农业', 'P': '林业',
            # 省市地区映射
            'q': '北京', 'N': '天津', 'p': '河北', 'G': '山西', 'L': '内蒙古',
            'r': '辽宁', 'B': '黑龙江', 'C': '吉林', 'D': '上海', 'k': '江苏',
            'x': '浙江', 'y': '安徽', 'z': '江西', 'F': '福建', 'n': '山东',
            'o': '河南', 'v': '湖北', 'w': '湖南', 'u': '广东', 'I': '广西',
            'Q': '海南', 't': '四川', 'J': '贵州', 'H': '云南', 'S': '青海',
            'T': '西藏', 's': '陕西', 'K': '甘肃', 'R': '宁夏', 'O': '新疆',
            'M': '重庆'
        }
        return mapping
    
    def extract_data(self, content, mapping):
        # 使用正则表达式提取各字段数据
        school_names = re.findall(r'univNameCn:"([^"]+)"', content)
        provinces = re.findall(r'province:([a-zA-Z])', content)
        categories = re.findall(r'univCategory:([a-zA-Z])', content)
        scores_raw = re.findall(r'score:([a-zA-Z\d.]+)', content)
        
        universities = []
        # 遍历所有学校,组装完整数据
        for i in range(len(school_names)):
            # 通过映射表将字母代码转换为实际值
            province = mapping.get(provinces[i], '未知')
            category = mapping.get(categories[i], '未知')
            
            # 处理分数:
            score_raw = scores_raw[i] if i < len(scores_raw) else '0.0'
            try:
                score = float(score_raw) if score_raw.replace('.', '').isdigit() else 0.0
            except:
                score = 0.0
            
            universities.append({
                '序号': i + 1,
                '排名': i + 1,
                '学校名称': school_names[i],
                '省市': province,
                '类型': category,
                '总分': score
            })
        
        return universities
    
    def save_data(self, universities):
        # 保存到SQLite数据库
        conn = sqlite3.connect('university_ranking_2021.db')
        cursor = conn.cursor()
        cursor.execute('DROP TABLE IF EXISTS universities')
        cursor.execute('CREATE TABLE universities (序号 INTEGER, 排名 INTEGER, 学校名称 TEXT, 省市 TEXT, 类型 TEXT, 总分 REAL)')
        for uni in universities:
            cursor.execute('INSERT INTO universities VALUES (?, ?, ?, ?, ?, ?)',
                         (uni['序号'], uni['排名'], uni['学校名称'], uni['省市'], uni['类型'], uni['总分']))
        conn.commit()
        conn.close()
        
        # 同时保存到CSV文件
        with open('university_ranking_2021.csv', 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.DictWriter(f, fieldnames=['序号', '排名', '学校名称', '省市', '类型', '总分'])
            writer.writeheader()
            writer.writerows(universities)
        
        print(f"已保存 {len(universities)} 所大学数据")
    
    def display(self, universities, n=60):
        # 以表格形式显示前n所大学数据
        print("\n" + "=" * 100)
        print(f"{'排名':<6}{'学校名称':<20}{'省市':<8}{'类型':<6}{'总分':<8}")
        print("=" * 100)
        for uni in universities[:n]:
            print(f"{uni['排名']:<8}{uni['学校名称']:<22}{uni['省市']:<10}{uni['类型']:<8}{uni['总分']:<8.1f}")
        print("=" * 100)
        print(f"总共 {len(universities)} 所大学(显示前 {n} 所)")
    
    def run(self):
        # 主执行流程
        content = self.get_js_content()           # 1. 获取数据
        mapping = self.build_mapping()            # 2. 构建映射表
        universities = self.extract_data(content, mapping)  # 3. 提取数据
        self.save_data(universities)              # 4. 存储数据
        self.display(universities)                # 5. 显示结果

if __name__ == "__main__":
    # 启动爬虫
    UniversitySpider().run()
结果: 控制台打印前60所学校

image

数据库和csv文件保存全部信息
image
实验心得
这一次我们通过构建映射表将字母变量转换为实际省市和类型信息,并使用正则表达式提取结构化数据,成功提取了582所学校的信息,了解到了映射这么一个js文件的保存方式,此外我发现原网站的js文件中分数有少部分采用两位字母的变量名储存,但是很奇怪没有一个有规律的映射关系,可能需要手动一个一个对应?,但总体还是顺利爬取了学校信息,学会了如何用抓包和读取js文件的方式实现爬虫。

gitee仓库链接
https://gitee.com/yaya-xuan/zyx_project/tree/master/数据采集/作业2

posted on 2025-11-07 14:34  宋宋宋芽  阅读(4)  评论(0)    收藏  举报