資料採集實踐第三次作業

FungusTanion發表於2024-11-26

作業①

要求:指定一個網站,爬取這個網站中的所有的所有圖片,例如:中國氣象網(http://www.weather.com.cn)。使用scrapy框架分別實現單執行緒和多執行緒的方式爬取。
–務必控制總頁數(學號尾數2位)、總下載的圖片數量(尾數後3位)等限制爬取的措施。

1.作業內容

點選檢視程式碼
from bs4 import BeautifulSoup
from bs4 import UnicodeDammit
import urllib.request
import sqlite3

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):
        pass


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_span = li.select('p[class="tem"] span')
                    temp_i = li.select('p[class="tem"] i')
                    temp = (temp_span[0].text if temp_span else '') + "/" + (temp_i[0].text if temp_i else '')
                    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)

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

ws = WeatherForecast()
ws.process(["北京", "上海", "廣州", "深圳"])
print("completed")

2.心得體會

在處理網頁資料抓取任務時,我們不可避免地會面臨網頁結構變化帶來的挑戰。這次實驗讓我深刻認識到編寫靈活且健壯的程式碼的重要性,尤其是在面對可能隨時變化的網頁結構時。實驗初期,我們遇到了由於HTML結構修改導致的選擇器失效問題,特別是對於溫度資訊的提取部分。這不僅突顯了網頁結構的不穩定性,也強調了選擇器設計的靈活性。在編寫選擇器時,我們應儘量避免硬編碼具體的標籤或類名,而是採用更通用的選擇器或結合多個選擇器來提高匹配的準確性。
溫度資訊可能會出現的不同

作業②

要求:熟練掌握 scrapy 中 Item、Pipeline 資料的序列化輸出方法;使用scrapy框架+Xpath+MySQL資料庫儲存技術路線爬取股票相關資訊。

1.作業內容

點選檢視程式碼
import requests
import sqlite3
import json
import logging

# 配置日誌記錄
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 資料庫操作類
class StockDatabase:
    def __init__(self, db_name):
        self.conn = sqlite3.connect(db_name)
        self.create_table()
        logging.info(f"Database '{db_name}' connected.")

    def create_table(self):
        """建立儲存股票資訊的表格"""
        c = self.conn.cursor()
        c.execute('''
            CREATE TABLE IF NOT EXISTS stock_info (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                stock_code TEXT,
                stock_name TEXT,
                latest_price REAL,
                change_percent REAL,
                change_amount REAL,
                volume TEXT,
                turnover TEXT,
                amplitude REAL,
                high REAL,
                low REAL,
                open_price REAL,
                yesterday_close REAL
            )
        ''')
        self.conn.commit()
        logging.info("Table 'stock_info' is ready.")

    def save_stock_data(self, stock_data):
        """將股票資料儲存到資料庫"""
        c = self.conn.cursor()
        for stock in stock_data:
            stock_record = (
                stock.get('f12'),  # 股票程式碼
                stock.get('f14'),  # 股票名稱
                stock.get('f2'),   # 最新報價
                stock.get('f3'),   # 漲跌幅
                stock.get('f4'),   # 漲跌額
                stock.get('f5'),   # 成交量
                stock.get('f6'),   # 成交額
                stock.get('f7'),   # 振幅
                stock.get('f15'),  # 最高價
                stock.get('f16'),  # 最低價
                stock.get('f17'),  # 今開
                stock.get('f18')   # 昨收
            )
            try:
                c.execute('''
                    INSERT INTO stock_info 
                    (stock_code, stock_name, latest_price, change_percent, change_amount, volume, turnover, amplitude, high, low, open_price, yesterday_close)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', stock_record)
                logging.info(f"Stock data for '{stock.get('f14')}' saved.")
            except Exception as e:
                logging.error(f"Error saving stock data: {e}")
        self.conn.commit()

    def display_stock_data(self):
        """顯示資料庫中的股票資料"""
        c = self.conn.cursor()
        c.execute("SELECT * FROM stock_info")
        rows = c.fetchall()

        # 列印表頭
        print(f"{'序號':<5} {'股票程式碼':<10} {'股票名稱':<10} {'最新報價':<10} {'漲跌幅':<10} {'漲跌額':<10} {'成交量':<10} {'成交額':<15} {'振幅':<10} {'最高':<10} {'最低':<10} {'今開':<10} {'昨收':<10}")

        # 列印每行資料
        for row in rows:
            latest_price = float(row[3]) if row[3] not in ('-', None) else 0.0
            change_percent = float(row[4]) if row[4] not in ('-', None) else 0.0
            change_amount = float(row[5]) if row[5] not in ('-', None) else 0.0
            amplitude = float(row[8]) if row[8] not in ('-', None) else 0.0
            high = float(row[9]) if row[9] not in ('-', None) else 0.0
            low = float(row[10]) if row[10] not in ('-', None) else 0.0
            open_price = float(row[11]) if row[11] not in ('-', None) else 0.0
            yesterday_close = float(row[12]) if row[12] not in ('-', None) else 0.0

            print(f"{row[0]:<5} {row[1]:<10} {row[2]:<10} "
                  f"{latest_price:<10.2f} {change_percent:<10.2f} {change_amount:<10.2f} "
                  f"{row[6]:<10} {row[7]:<15} {amplitude:<10.2f} {high:<10.2f} "
                  f"{low:<10.2f} {open_price:<10.2f} {yesterday_close:<10.2f}")

    def close_connection(self):
        """關閉資料庫連線"""
        self.conn.close()
        logging.info("Database connection closed.")

# 獲取股票資料
def get_stock_data():
    url = 'https://push2.eastmoney.com/api/qt/clist/get?cb=jQuery112409840494931556277_1633338445629&pn=1&pz=10&po=1&np=1&fltt=2&invt=2&fid=f3&fs=b:MK0021&fields=f12,f14,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f18,f15,f16,f17,f23'
    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 Edg/129.0.0.0'
    }
    response = requests.get(url, headers=headers)

    # 去除不必要的字元,提取有效的JSON部分
    response_text = response.text.split('(', 1)[1].rsplit(')', 1)[0]

    stock_data = json.loads(response_text)['data']['diff']  # 解析JSON並提取有用的欄位
    return stock_data

# 主函式
def main():
    # 建立資料庫連線
    db = StockDatabase('eastmoney_stock.db')

    # 獲取股票資料
    stock_data = get_stock_data()

    # 儲存資料到資料庫
    db.save_stock_data(stock_data)

    # 顯示錶格資料
    db.display_stock_data()

    # 關閉資料庫連線
    db.close_connection()

if __name__ == '__main__':
    main()

2.心得體會

我使用requests和BeautifulSoup庫來爬取股票資料,並將其儲存在SQLite資料庫中。這次經歷讓我深刻體會到網路爬蟲的複雜性和資料處理的重要性。編寫爬蟲時,我必須仔細分析網頁結構,並編寫能夠適應變化的程式碼。處理資料時,我學會了如何高效地解析和清洗資料,並將其儲存在資料庫中,以便後續的查詢和分析。此外,實驗還讓我意識到異常處理和日誌記錄的重要性,它們對於除錯和維護爬蟲程式至關重要。

作業③

要求:熟練掌握 scrapy 中 Item、Pipeline 資料的序列化輸出方法;使用scrapy框架+Xpath+MySQL資料庫儲存技術路線爬取外匯網站資料。

1.作業內容

點選檢視程式碼
import urllib.request
from bs4 import BeautifulSoup
import sqlite3

# 目標網址
url = "https://www.shanghairanking.cn/rankings/bcur/2021"

# 使用BeautifulSoup解析HTML內容
resp = urllib.request.urlopen(url)
html = resp.read()
soup = BeautifulSoup(html, 'html.parser')

# 找到包含排名資訊的表格行
rows = soup.find_all('tr')

# 列印標題
print(f"排名\t學校名稱{'':<6}\t省市{'':<1}\t學校型別\t總分")

# 連線到SQLite資料庫(如果不存在則建立)
conn = sqlite3.connect('rank2021.db')
cursor = conn.cursor()

# 建立表
cursor.execute('''
CREATE TABLE IF NOT EXISTS university_rank (
    rank TEXT,
    uni_name TEXT,
    province TEXT,
    uni_type TEXT,
    score TEXT
)
''')

# 遍歷表格的每一行,提取資訊並儲存到資料庫
for row in rows:
    # 找到排名
    rank = row.find('td').text.strip() if row.find('td') else ""

    # 找到學校名稱的span
    span = row.find('span', class_="name-cn")
    uni_name = span.text.strip() if span else ""

    # 找到剩餘內容
    province = row.find_all('td')[2].text.strip() if row.find('td') else ""
    uni_type = row.find_all('td')[3].text.strip() if row.find('td') else ""
    score = row.find_all('td')[4].text.strip() if row.find('td') else ""

    # 列印提取的資訊
    print(f"{rank}\t{uni_name:<10}\t{province:<3}\t{uni_type:<4}\t{score}")

    # 插入資料到資料庫
    cursor.execute('''
    INSERT INTO university_rank (rank, uni_name, province, uni_type, score)
    VALUES (?, ?, ?, ?, ?)
    ''', (rank, uni_name, province, uni_type, score))

# 提交事務
conn.commit()

# 關閉資料庫連線
conn.close()

2.心得體會

本次實驗中我分析了目標網頁的結構,確定了需要爬取的資料欄位,包括學校排名、名稱、型別、所在地、總分以及各個評價指標等。然後,我編寫了爬蟲指令碼,,提取所需的資料。在資料提取過程中,我特別注意了資料的清洗和轉換,確保資料的準確性和一致性。我將提取的資料儲存到SQLite資料庫中。建立了一個資料庫和相應的表結構,並編寫了插入資料的SQL語句。在資料儲存過程中,我同樣注重了資料的完整性和錯誤處理,確保即使在遇到異常情況時,程式也能優雅地處理錯誤並繼續執行。為了記錄整個分析過程,我使用了瀏覽器的開發者工具(F12)來除錯和檢視網路請求。我錄製了分析過程,並使用工具將錄製的影片轉換為GIF圖片。這樣不僅方便了部落格的讀者理解整個爬蟲的工作流程,也使得部落格內容更加生動和直觀。透過這次實驗,我深刻體會到了網路爬蟲技術在資料獲取中的重要性,以及資料處理和儲存的複雜性。我學會了如何分析網頁結構,編寫高效的爬蟲指令碼,以及如何將資料儲存到資料庫中。此外,我也認識到了資料清洗和錯誤處理的重要性,它們是保證資料質量和程式穩定性的關鍵。

相關文章