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)




实验结果

屏幕截图 2025-11-04 194458

心得体会

这次实验种学会了,使用模拟浏览器User-Agent的方法来发送请求,稳定性更强;同时在使用BeautifulSoup解析时,对于温度标签的处理要更加留意,有些结构不一致,就要正确使用if/else的方法分开讨论,同时可以加入一些调试信息和输出,可以更加直观查看运行结果。

第二题

要求

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

核心代码
  1. 相对于在普通的HTML里面进行爬取,更应该去分析Network面板中的XHR请求。爬取API返回的JSON数据,要更加稳定,数据更加完整,关键在于正确找到js文件,然后对其进行处理。
    image
  2. 在发送请求时,我们仅仅使用模拟浏览器User-Agent的方法来发送请求,仍然会有被屏蔽的风险,所以,可以再加上cookies,相当于你在东方财富网的"身份证",服务器通过它们来:识别你是合法用户而非恶意爬虫,维持你的登录状态和会话。
  3. 对于我们使用的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
  1. 对于爬取出来的数据,数字正确,形式未必正确,因此我们对数据进行格式处理,进行数据计算和单位转换
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
部分运行结果

屏幕截图 2025-11-04 200055

屏幕截图 2025-11-04 200108

心得体会

通过这次实验,我学会了通过URL参数精确控制数据的获取范围。URL字符串中,每一个参数都有其特定含义:比如:pn控制分页,fs指定市场类型,fields定义需要返回的字段。这种参数化的请求方式让我能够精准地获取所需数据,避免了传统爬取中"获取全部再过滤"的资源浪费。特别是在分页处理上,通过简单的页面参数递增,就实现了大规模数据的批量获取,同时直接获取结构化的JSON数据,跳过了繁琐的HTML解析环节。这不仅大大提高了代码的执行效率,还显著提升了程序的稳定性。但是需要花时间找到真正的数据接口。

第三题

要求

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

核心代码
  1. 先使用正则表达式专门识别和解析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 []
部分运行结果

image

找到正确的API接口(gif)

76D79A33A45936437BEE3BE64746C4DD

心得体会

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

Gitee链接 https://gitee.com/XWJ_777/data-collection
posted @ 2025-11-04 21:38  XWJ_777  阅读(18)  评论(0)    收藏  举报