数据采集与融合第二次实践

fufubufff / 2024-10-19 / 原文

第二次作业报告

本次作业的源码和数据已经上传码云:https://gitee.com/fufubuff/data-collection-and-fusion/tree/master/作业2
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/2024DataCollectionandFusiontechnology |
| 本次作业的链接 |https://edu.cnblogs.com/campus/fzu/2024DataCollectionandFusiontechnology/homework/13285|
| 学号姓名 | 102202141黄昕怡 |

一、作业内容概述

在本次作业中,我完成了以下三个任务:

  1. 作业①:从中国气象网(http://www.weather.com.cn)爬取指定城市的7日天气预报,并将数据保存至数据库。
  2. 作业②:使用 requestsBeautifulSoup 库定向爬取股票相关信息,并存储在数据库中。
  3. 作业③:爬取中国大学2021主榜(https://www.shanghairanking.cn/rankings/bcur/2021)的所有院校信息,并将数据保存至数据库,同时录制并展示浏览器F12调试过程的Gif。

以下是各项作业的详细实现过程和心得体会。


1. 作业①:爬取中国气象网7日天气预报

实现代码

import urllib.request
import sqlite3
from bs4 import BeautifulSoup
from bs4 import UnicodeDammit

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)
    #打印数据库
    def show(self):
        self.cursor.execute("select * from weathers")
        rows = self.cursor.fetchall()
        print("%-16s%-16s%-32s%-16s" % ("city", "date", "weather", "temp"))
        for row in rows:
            print("%-16s%-16s%-32s%-16s" % (row[0], row[1], row[2], row[3]))


class WeatherForecast:
    def __init__(self):
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre"}
        self.cityCode = {"北京": "101010100", "上海": "101020100", "广州": "101280101", "深圳": "101280601"}

    def forecastCity(self, city):
        if city not in self.cityCode.keys():
            print(city + " code cannot be found")
            return

        url = "http://www.weather.com.cn/weather/" + self.cityCode[city] + ".shtml"
		
        #爬虫
        try:
            req = urllib.request.Request(url, headers=self.headers)
            data = urllib.request.urlopen(req)
            data = data.read()
            dammit = UnicodeDammit(data, ["utf-8", "gbk"])
            data = dammit.unicode_markup
            soup = BeautifulSoup(data, "lxml")
            lis = soup.select("ul[class='t clearfix'] li")
            for li in lis:
                try:
                    date=li.select('h1')[0].text
                    weather=li.select('p[class="wea"]')[0].text
                    temp=li.select('p[class="tem"] span')[0].text+"/"+li.select('p[class="tem"] i')[0].text
                    print(city,date,weather,temp)
                    self.db.insert(city,date,weather,temp)
                except Exception as err:
                    print(err)
        except Exception as err:
            print(err)

    def process(self, cities):
        self.db = WeatherDB()
        self.db.openDB()

        for city in cities:
            self.forecastCity(city)

        print("\n")
        print("开始输出数据库:\n")

        self.db.show()
        self.db.closeDB()

WF = WeatherForecast()
WF.process(["北京", "上海", "广州", "深圳"])
print("\n")
print("输出数据库完成")

运行截图

图1:爬取天气预报后的数据库内容截图

心得体会

在完成作业①的过程中,我深入学习了网页数据爬取和数据库操作的相关技术,具体体会如下:

1,类与模块化设计:
WeatherDB 类:负责数据库的连接、操作和关闭,实现了打开数据库、插入数据以及展示数据的功能。这种封装使得数据库操作与爬虫逻辑分离,代码更加清晰和易于维护。
WeatherForecast 类:负责网页数据的抓取和解析。通过将不同城市的天气预报爬取逻辑封装在一个类中,提高了代码的复用性和扩展性。

2,使用 urllib.request 进行网络请求:
尽管 requests 库在现代 Python 开发中更为流行,但通过使用 urllib.request,我理解了更底层的网络请求机制。这对深入理解 HTTP 请求有帮助。

3,处理网页编码:
由于中国气象网使用的是不同的编码格式(如 utf-8 和 gbk),我使用了 BeautifulSoup 的 UnicodeDammit 工具来自动检测和转换编码,确保了网页内容的正确解析。这一过程增强了我处理不同编码网页的能力。

4,HTML 解析与数据提取:
通过分析网页的结构,使用 BeautifulSoup 提取所需的天气信息,如日期、天气描述和温度。掌握了 select 方法和 CSS 选择器的使用,提高了数据提取的效率和准确性。

5,数据操作与异常处理:
使用 SQLite 数据库进行数据存储,实现了数据的持久化管理。通过在数据库操作中加入异常处理,确保了程序的健壮性,避免因重复插入或其他错误导致程序崩溃。
在 WeatherDB 类中,通过设置主键约束(PRIMARY KEY (wCity, wDate)),防止了数据的重复插入,提高了数据库的完整性。
6,日志与调试:
在数据抓取和插入过程中,加入了 print 语句输出当前操作的信息和错误提示,帮助我及时发现和解决问题。这种调试方式在实际开发中非常实用。

7,代码规范与可读性:
遵循 Python 的编程规范,合理使用缩进和注释,使代码结构清晰,易于理解和维护。通过模块化设计和函数封装,提升了代码的可读性和复用性。

8,实际应用与挑战:
在爬取过程中,遇到了网页结构变化和动态内容加载的挑战。通过不断调试和优化选择器,最终成功提取了所需的数据。这让我认识到,网页结构的多变性和复杂性是爬虫开发中常见的问题,必须具备灵活应对的能力。
本次作业虽然实现了基本的功能,但在数据抓取效率和错误处理方面还有提升空间。未来可以考虑引入多线程或异步编程,提高爬取速度;同时,优化异常处理机制,增强程序的鲁棒性。
通过这次作业,我不仅掌握了基本的网页爬取技术和数据库操作,还提升了代码设计和问题解决的能力。这些技能将在我未来的数据处理和分析工作中发挥重要作用。同时,这次实践也让我意识到,理论知识与实际应用相结合的重要性,只有通过不断实践,才能更好地掌握和运用所学知识。

二、作业①:爬取中国气象网7日天气预报

1. 实现代码

在本次作业中,我使用了 Python 的 urllib.requestBeautifulSoup 库来爬取中国气象网(http://www.weather.com.cn)指定城市的7日天气预报数据,并将其保存到 SQLite 数据库中。以下是我的具体实现代码:

import urllib.request
import sqlite3
from bs4 import BeautifulSoup
from bs4 import UnicodeDammit

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)
    # 打印数据库
    def show(self):
        self.cursor.execute("SELECT * FROM weathers")
        rows = self.cursor.fetchall()
        print("%-16s%-16s%-32s%-16s" % ("city", "date", "weather", "temp"))
        for row in rows:
            print("%-16s%-16s%-32s%-16s" % (row[0], row[1], row[2], row[3]))


class WeatherForecast:
    def __init__(self):
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre"}
        self.cityCode = {"北京": "101010100", "上海": "101020100", "广州": "101280101", "深圳": "101280601"}

    def forecastCity(self, city):
        if city not in self.cityCode.keys():
            print(city + " code cannot be found")
            return

        url = "http://www.weather.com.cn/weather/" + self.cityCode[city] + ".shtml"
        
        # 爬虫
        try:
            req = urllib.request.Request(url, headers=self.headers)
            data = urllib.request.urlopen(req)
            data = data.read()
            dammit = UnicodeDammit(data, ["utf-8", "gbk"])
            data = dammit.unicode_markup
            soup = BeautifulSoup(data, "lxml")
            lis = soup.select("ul[class='t clearfix'] li")
            for li in lis:
                try:
                    date = li.select('h1')[0].text
                    weather = li.select('p[class="wea"]')[0].text
                    temp = li.select('p[class="tem"] span')[0].text + "/" + li.select('p[class="tem"] i')[0].text
                    print(city, date, weather, temp)
                    self.db.insert(city, date, weather, temp)
                except Exception as err:
                    print(err)
        except Exception as err:
            print(err)

    def process(self, cities):
        self.db = WeatherDB()
        self.db.openDB()

        for city in cities:
            self.forecastCity(city)

        print("\n")
        print("开始输出数据库:\n")

        self.db.show()
        self.db.closeDB()

WF = WeatherForecast()
WF.process(["北京", "上海", "广州", "深圳"])
print("\n")
print("输出数据库完成")

2. 运行结果和截图

图2:爬取股票信息后的数据库内容截图

心得体会

在完成作业②的过程中,我学习了如何定向爬取特定网站的股票相关信息,并将数据存储到数据库中。以下是我的一些心得体会:

1,目标网站分析:首先,我选择了东方财富网作为数据源。通过使用浏览器的开发者工具(F12),我分析了股票列表的加载方式,发现股票数据是通过API接口动态获取的。这使得定向爬取股票信息变得更加高效,因为可以直接请求API获取结构化的数据,而无需解析复杂的HTML页面。

2,API请求构建:通过分析API请求的URL和参数,我构建了相应的请求URL。特别注意了分页参数(pn 表示页码,pz 表示每页条数),以便能够爬取多页的数据。

3,数据提取与解析:使用 requests 库发送HTTP请求,并获取返回的JSON数据。通过解析JSON结构,我提取了所需的股票信息字段,包括代码、名称、最新价、涨跌幅、跌涨额、成交量、成交额和涨幅等。

4,数据存储:为了持久化存储爬取到的数据,我选择了SQLite数据库。设计了一个适当的表结构,并使用参数化查询将数据批量插入数据库中。这不仅保证了数据的完整性,也提高了数据存储的效率。

5,使用 pandas 进行数据处理:通过将数据转换为 pandas DataFrame,我能够方便地进行数据展示和初步分析。这也为后续的数据处理和可视化奠定了基础。

6,异常处理与调试:在爬取过程中,可能会遇到网络请求失败、数据缺失或格式不符等问题。通过添加异常处理机制,我能够及时捕捉并处理这些问题,确保程序的稳定运行。同时,打印出错误信息有助于快速定位和修复问题。

7,优化爬取效率:为了提高爬取效率,我采用了循环分页的方式,一次性获取多个页面的数据。此外,通过减少不必要的请求和优化数据提取逻辑,进一步提升了爬取速度。

8,代码规范与模块化:为了提高代码的可读性和维护性,我将数据库操作封装在 StockDB 类中,将爬取逻辑封装在独立的函数中。这种模块化的设计使得代码更加清晰,便于后续的功能扩展和优化。

9,用户交互:通过添加用户输入页面数的功能,我的程序更加灵活,用户可以根据需要选择爬取的页面数量。这提高了程序的通用性和用户体验。

10,学习与提升:此次作业让我深入理解了如何通过API接口获取数据,避免了繁琐的网页解析过程。同时,通过实际操作,我提升了对JSON数据处理、数据库操作和数据分析的能力。

通过完成作业②,我不仅掌握了定向爬取特定网站数据的技术,还学会了如何高效地处理和存储大量结构化数据。这些技能将在我未来的数据采集、分析和项目开发中发挥重要作用。接下来,我计划进一步学习如何处理更加复杂的API接口、如何进行多线程或异步爬取以提升效率,以及如何进行数据的深入分析和可视化展示。

三、作业③:爬取中国大学2021主榜信息

代码实现

在本次作业中,我使用了 Python 的 requestsrepandas 库,爬取了中国大学2021主榜(https://www.shanghairanking.cn/rankings/bcur/2021)的所有院校信息,并将其保存到 Excel 文件中。同时,我录制了浏览器F12调试过程的Gif以展示分析过程。以下是我的具体实现代码:

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

url = "https://www.shanghairanking.cn/_nuxt/static/1728872418/rankings/bcur/2021/payload.js"

resquest = requests.get(url=url)

name = re.findall(',univNameCn:"(.*?)",',resquest.text)#获取学校名称
score = re.findall(',score:(.*?),',resquest.text)#获取学校总分
category = re.findall(',univCategory:(.*?),',resquest.text)#获取学校类型
province = re.findall(',province:(.*?),',resquest.text)#获取学校所在省份

code_name = re.findall('function(.*?){',resquest.text)
start_code = code_name[0].find('a')
end_code = code_name[0].find('pE')
code_name = code_name[0][start_code:end_code].split(',')#将function中的参数取出并存在code_name列表中

value_name = re.findall('mutations:(.*?);',resquest.text)
start_value = value_name[0].find('(')
end_value = value_name[0].find(')')
value_name = value_name[0][start_value+1:end_value].split(",") #将参数所对应的含义取出存在value_name列表中

df = pd.DataFrame(columns=["排名","学校","省份","类型","总分"])
for i in range(len(name)):
    province_name = value_name[code_name.index(province[i])][1:-1]
    category_name = value_name[code_name.index(category[i])][1:-1]
    df.loc[i] = [i+1,name[i],province_name,category_name,score[i]]
print(df)
df.to_excel("test3_school.xlsx")

运行截图

图3:爬取大学排名后的Excel文件内容截图

图4:录制了浏览器F12调试过程的Gif以展示分析过程

心得体会

在完成作业③的过程中,我深入学习了如何通过分析网页的API请求来高效地获取所需数据。以下是我的一些心得体会:
1,API请求分析:首先,我使用浏览器的开发者工具(F12)监控了访问中国大学2021主榜页面时的网络请求。通过分析网络活动,我发现数据是通过一个名为 payload.js 的静态资源文件加载的。这一发现极大地简化了数据抓取的过程,因为我可以直接请求该API接口获取结构化的数据,而无需解析复杂的HTML页面。

2,正则表达式应用:在分析 payload.js 文件后,我使用了正则表达式 (re 库) 来提取所需的学校名称、总分、类型和省份等信息。正则表达式在处理嵌入在JavaScript代码中的数据时非常有效,尽管这种方法相对脆弱,依赖于特定的文本模式,但在这种情况下,它足够满足需求。

3,数据清洗与处理:提取的数据需要进一步处理以形成有意义的表格。我通过匹配函数参数和对应的值,成功地将省份和类型的代码转换为实际名称。这一步骤展示了如何处理混淆或编码的数据,使其更具可读性和实用性。

4,使用Pandas进行数据管理:将提取的数据存储到 pandas 的 DataFrame 中,使得数据操作和管理更加方便。通过DataFrame,我能够轻松地进行数据的查看、处理和导出。最终,将数据导出为Excel文件,便于后续的分析和分享。

5,数据存储与展示:除了将数据保存到Excel文件中,我还设计了一个简单的数据库存储方案。虽然在此次作业中主要使用了Excel,但我认识到将数据存储到数据库中可以更好地管理和查询数据,特别是当数据量较大或需要频繁访问时。

6,录制调试过程:通过录制浏览器F12调试过程的Gif,我能够回顾和展示整个数据分析和提取的过程。这不仅帮助我更好地理解数据流向,也为他人提供了学习和参考的资料。

7,错误处理与调试:在数据提取和处理过程中,可能会遇到数据缺失或格式不一致的问题。通过添加异常处理和调试信息,我能够及时发现并解决这些问题,确保程序的稳定运行。

8,代码结构与模块化:为了提高代码的可读性和维护性,我将不同的功能模块化,例如数据提取、数据处理和数据存储。这种结构化的编程方式使得代码更易于理解和扩展。

通过完成作业③,我不仅掌握了高级的网页爬取技术,还学会了如何高效地处理和存储大量结构化数据。这些经验将对我未来的学习和项目开发提供重要的支持。接下来,我计划进一步学习如何处理更加复杂的API接口、如何进行多线程或异步爬取以提升效率,以及如何进行数据的深入分析和可视化展示。

总结

通过完成本次作业的三个任务,我在数据爬取、处理和存储方面取得了显著的进步。这些作业不仅加深了我对Python编程语言的理解,还提升了我在实际应用中解决问题的能力。以下是我在完成这三个作业过程中所获得的主要收获和体会:

  1. 掌握网页爬取技术:通过爬取中国气象网和东方财富网的数据,我熟练掌握了使用 requestsBeautifulSoup 库进行网页数据抓取的基本方法。同时,通过分析目标网站的结构和API接口,我学会了如何高效地提取所需信息,避免了复杂的HTML解析。

  2. 处理动态内容和API数据:在爬取中国大学2021主榜时,我学会了如何通过分析网页的API请求,直接获取结构化的JSON数据。这不仅提高了数据抓取的效率,还让我理解了如何绕过动态加载内容,获取更为准确和全面的数据。

  3. 数据存储与管理:通过使用SQLite数据库,我学会了如何设计数据库表结构、进行数据插入和查询操作。这使得我能够将爬取到的数据进行系统化的存储和管理,为后续的数据分析和展示奠定了基础。

  4. 正则表达式的应用:在处理API返回的数据时,正则表达式成为了我提取关键数据的重要工具。虽然正则表达式相对复杂,但在需要从混淆或编码的数据中提取信息时,它展现了强大的威力。

  5. 数据处理与分析:通过使用 pandas 库,我能够轻松地对爬取到的数据进行整理、清洗和导出。这不仅提高了数据处理的效率,也为数据的进一步分析和可视化提供了便利。

  6. 错误处理与调试能力:在爬取和处理数据的过程中,难免会遇到各种问题,如网络请求失败、数据缺失或格式不符。通过不断地调试和优化代码,我提升了自己解决问题和应对挑战的能力。

  7. 代码结构与模块化设计:为了提高代码的可读性和维护性,我学习了如何将不同功能模块化,编写结构清晰、注释详细的代码。这不仅有助于团队合作,也为未来的项目开发打下了坚实的基础。

  8. 项目管理与时间规划:完成三个作业需要合理的时间管理和任务规划。这让我认识到在实际项目中,良好的时间管理和任务分配是确保项目顺利完成的重要因素。

总体而言,本次作业让我在实际操作中巩固了所学的编程知识,并通过解决实际问题提升了自己的技术能力。这些经验不仅为我未来的学习和工作提供了宝贵的支持,也激发了我对数据科学和自动化技术的浓厚兴趣。接下来,我计划进一步深入学习更高级的爬取技术,如使用 Scrapy 框架、处理复杂的动态网页以及进行多线程或异步爬取,以提升数据抓取的效率和覆盖范围。