2024資料採集與融合技術實踐-作業3

starryship發表於2024-11-12

一、中國氣象網單執行緒與多執行緒爬取圖片

碼雲倉庫:作業3/task1 · 曹星才/2022級資料採集與融合技術 - 碼雲 - 開源中國

(一)步驟

爬取網站https://p.weather.com.cn/tqxc/index.shtml

1.1 單執行緒方式爬取

step1:設定為單執行緒方式爬取,settings.py中注設定最大併發請求數量CONCURRENT_REQUESTS=1

step2:找到圖片對應的網站,傳送請求

# 方法:生成初始請求併傳送到目標網址
def start_requests(self):
    # 迴圈遍歷目標網站的前 5 頁
    for page in range(1, 6):
        # 第一頁的 URL 結構不同,需要單獨處理
        if page == 1:
            url = "https://p.weather.com.cn/tqxc/index.shtml"
        else:
            url = f"https://p.weather.com.cn/tqxc/index_{page}.shtml"

        # 使用 scrapy.Request 傳送請求,獲取頁面內容
        # 該請求的響應將由 'parse' 方法處理
        yield scrapy.Request(url=url, callback=self.parse)

step3:對請求得到的網頁進行解析,使用img_urls = selector.xpath('//img/@src')獲取該頁的所有圖片url,使用yield scrapy.Request(url=img_src, callback=self.download)逐個url呼叫download圖片下載函式

# 方法:解析天氣頁面的 HTML 響應
def parse(self, response, *args, **kwargs):
    data = response.body.decode('utf-8')  # 將響應的位元組資料解碼為 UTF-8 字串

    # 使用 Scrapy 的 Selector 類來解析解碼後的 HTML 資料
    selector = scrapy.Selector(text=data)

    # 提取頁面中的所有影像 URL
    img_urls = selector.xpath('//img/@src')

    # 遍歷所有提取的影像 URL
    for img_url in img_urls:
        img_src = img_url.extract()  # 獲取每個影像 URL 的字串形式
        print(img_src)  # 列印影像 URL(用於除錯)
        if img_src:  # 檢查影像 URL 是否有效
            # 傳送請求下載影像,下載完成後呼叫 'download' 方法
            yield scrapy.Request(url=img_src, callback=self.download)

step4:處理圖片的下載請求,建立WeatherItem例項,儲存影像二進位制資料,將獲取到的二進位制資料傳給item

# 方法:處理影像下載請求
def download(self, response):
    item = WeatherItem()  # 建立一個 WeatherItem 例項,用於儲存影像二進位制資料

    # 從響應中提取影像的二進位制內容
    img_binary = response.body

    # 將影像的二進位制資料存入 item
    item["img_binary"] = img_binary

    # 使用 yield 傳遞 item,將資料傳遞給資料管道或輸出
    yield item

step5:WeatherItem這隻用接收圖片的二進位制資料,傳給pipelines

class WeatherItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()

    img_binary = scrapy.Field()

step6:二進位制資料傳到pipelines,對資料進行儲存,儲存為JPG格式的圖片

# 定義一個處理爬取資料的管道類
class WeatherPipeline:
    count = 0  # 定義一個類變數,用於記錄下載的影像數量

    # 處理每個傳入的 item 物件
    def process_item(self, item, spider):
        # 每當處理一個 item 時,將計數器加 1
        WeatherPipeline.count += 1

        # 構造儲存影像的路徑
        # 每張影像使用遞增的數字作為檔名,並儲存在 "imgs" 資料夾中
        downloadPath = f"imgs/{WeatherPipeline.count}.jpg"

        # 以二進位制寫模式開啟檔案
        fobj = open(downloadPath, "wb")

        # 將影像的二進位制資料寫入檔案
        fobj.write(item["img_binary"])

        # 關閉檔案物件,確保資料被正確儲存
        fobj.close()

        # 列印下載完成的資訊,包括影像的位元組數
        print("下載完畢:", len(item["img_binary"]), "位元組")

        # 返回 item 物件,以便在 Scrapy 框架中繼續處理
        return item

step7scrapy crawl wea -s LOG_ENABLED=False執行專案

step8:得到單執行緒爬取的結果

1.2 多執行緒方式爬取

step9:設定為多執行緒方式爬取,settings.py中設定最大併發請求數量CONCURRENT_REQUESTS=32(其它數字也行)

step10scrapy crawl wea -s LOG_ENABLED=False執行專案

step11:得到多執行緒爬取的結果

(二)心得

在這次使用 Scrapy 進行中國氣象網單執行緒與多執行緒爬取圖片爬取的實驗中,透過單執行緒和多執行緒兩種方式分別爬取了天氣網站的圖片,積累了很多經驗和心得。

首先,在單執行緒方式下,設定了 CONCURRENT_REQUESTS=1,保證每次只傳送一個請求,逐一處理每個頁面的影像。這種方式的優勢在於操作簡單、易於除錯,可以清晰地控制每一步的執行過程。透過 scrapy.Request 傳送請求並解析網頁,獲取圖片 URL 後,再逐個請求下載圖片。在下載過程中,我利用 Scrapy 的 WeatherItem 儲存圖片的二進位制資料,並透過管道儲存為 JPG 格式的圖片。這種方法確保了圖片能夠按順序儲存,便於後期的管理。

然而,單執行緒的效率較低,特別是當網頁圖片較多時,下載速度會變得較慢。為了解決這個問題,我嘗試了多執行緒方式。在 settings.py 中將 CONCURRENT_REQUESTS 設定為 32,這樣 Scrapy 就能同時處理多個請求,大大提高了抓取和下載的效率。多執行緒方式雖然能加速爬取過程,但也增加了資源佔用,可能會導致伺服器負載過高,需要合理設定併發數。

總結來說,單執行緒適合小規模爬取或需要嚴格控制爬取速度的場景,而多執行緒則適合大規模爬取,可以顯著提高效率。透過這次實驗,我不僅加深了對 Scrapy 框架的理解,也對如何根據爬取任務的需求選擇合適的併發策略有了更清晰的認識。

二、股票相關資訊爬取

碼雲倉庫:作業3/task2 · 曹星才/2022級資料採集與融合技術 - 碼雲 - 開源中國

(一)步驟

step1:找到對應網站的url(https://quote.eastmoney.com/center/gridlist.html#hs_a_board),傳送請求

# 定義初始請求的方法
def start_requests(self):
    # 遍歷 start_urls 列表中的每個 URL
    for url in self.start_urls:
        # 生成 Scrapy 請求,並指定回撥函式 parse 處理響應
        yield scrapy.Request(url=url, callback=self.parse)

step2:直接解析網頁可以看到,輸出的data沒有我們想要的資訊,說明這個網站是js動態渲染的,這個時候就需要加入selenium

step3:在middlewares.py中設定中介軟體,在StockDownloaderMiddleware中新增我們的配置

為什麼選擇下載器中介軟體

  1. 處理動態內容:Selenium 是一個瀏覽器自動化工具,適合用來載入和處理由 JavaScript 渲染的動態網頁內容。透過在下載器中介軟體中使用 Selenium,你可以在請求傳送到目標網站之前或之後載入頁面並提取資料,然後再將處理後的響應交給 Scrapy 引擎或 Spider。
  2. 靈活性:將 Selenium 整合到下載器中介軟體中,可以靈活地控制哪些請求需要用 Selenium 處理,比如對一些特定的動態網頁,其他的普通網頁則仍然用 Scrapy 的預設下載器來處理。
class StockDownloaderMiddleware:
    # Scrapy 中的下載中介軟體類,用於自定義處理請求和響應

    def __init__(self):
        # 初始化方法,配置並啟動一個無頭(headless)Chrome 瀏覽器
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument('--headless')  # 執行無頭模式,不開啟瀏覽器視窗
        chrome_options.add_argument('--disable-gpu')  # 禁用 GPU(適用於某些無頭模式的情況)
        self.driver = webdriver.Chrome(options=chrome_options)  # 建立一個 Chrome 瀏覽器例項

    @classmethod
    def from_crawler(cls, crawler):
        # 用於建立中介軟體例項,並連線 spider_opened 訊號
        s = cls()  # 建立中介軟體例項
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)  # 連線爬蟲開啟時的訊號
        return s  # 返回中介軟體例項

    def process_request(self, request, spider):
        # 處理每個請求的方法
        self.driver.implicitly_wait(10)  # 設定隱式等待時間為 10 秒
        self.driver.get(request.url)  # 使用 Selenium 載入網頁
        body = self.driver.page_source  # 獲取網頁載入後的 HTML 原始碼

        # 建立並返回一個 HtmlResponse 物件,以提供給 Scrapy 進行後續解析
        return HtmlResponse(
            url=request.url,
            body=body,
            encoding='utf-8',
            request=request
        )

    def process_response(self, request, response, spider):
        # 處理響應的方法,返回處理後的響應物件
        return response  # 返回原始響應,未進行任何修改

    def process_exception(self, request, exception, spider):
        # 處理請求或下載過程中的異常
        pass  # 未處理異常,繼續異常鏈的處理

    def spider_opened(self, spider):
        # 當爬蟲被開啟時呼叫的方法
        spider.logger.info("Spider opened: %s" % spider.name)  # 記錄日誌,指示爬蟲已開啟

    def spider_closed(self, spider):
        # 當爬蟲關閉時呼叫的方法
        self.driver.quit()  # 關閉瀏覽器以釋放資源
        spider.logger.info("Browser closed for spider: %s" % spider.name)  # 記錄日誌,指示瀏覽器已關閉

settings.py中配置:

step4:然後我們就可以順利的獲取到網頁的動態資料了,觀察html結構

step5:在parse中對資料進行解析

def parse(self, response):
    # 將響應的位元組資料解碼為 UTF-8 字串
    data = response.body.decode('utf-8')

    # 使用 Scrapy 的 Selector 類解析 HTML 資料
    selector = scrapy.Selector(text=data)

    # 使用 XPath 定位包含股票資料的表格
    body = selector.xpath("//table[@id='table_wrapper-table']")

    # 提取表格中的所有行(<tr> 元素),忽略第一個沒用的
    trs = body.xpath("//tr")[1:]

    # 遍歷每個表格行,提取所需資料
    for tr in trs:
        # 建立一個 StockItem 例項,用於儲存提取的資料
        item = StockItem()

        # 提取每一列的資料,並使用 XPath 獲取相應的文字內容
        xuHao = tr.xpath("./td[1]/text()").get()  # 序號
        guPiaoDaiMa = tr.xpath("./td[2]//text()").get()  # 股票程式碼
        guPiaoNingCheng = tr.xpath("./td[3]//text()").get()  # 股票名稱
        zuiXingBaoJia = tr.xpath("./td[5]//text()").get()  # 最新報價
        zhangDieFu = tr.xpath("./td[6]//text()").get()  # 漲跌幅
        zhangDieE = tr.xpath("./td[7]//text()").get()  # 漲跌額
        chengJiaoLiang = tr.xpath("./td[8]//text()").get()  # 成交量
        zhenFu = tr.xpath("./td[10]//text()").get()  # 振幅
        zuiGao = tr.xpath("./td[11]//text()").get()  # 最高價
        zuiDi = tr.xpath("./td[12]//text()").get()  # 最低價
        jinKai = tr.xpath("./td[13]//text()").get()  # 今開價
        zuoShou = tr.xpath("./td[14]//text()").get()  # 昨收價

        # 將提取的資料存入 item 物件的相應欄位
        item['xuHao'] = xuHao
        item['guPiaoDaiMa'] = guPiaoDaiMa
        item['guPiaoNingCheng'] = guPiaoNingCheng
        item['zuiXingBaoJia'] = zuiXingBaoJia
        item['zhangDieFu'] = zhangDieFu
        item['zhangDieE'] = zhangDieE
        item['chengJiaoLiang'] = chengJiaoLiang
        item['zhenFu'] = zhenFu
        item['zuiGao'] = zuiGao
        item['zuiDi'] = zuiDi
        item['jinKai'] = jinKai
        item['zuoShou'] = zuoShou

        # 使用 yield 語句將 item 傳遞到管道進行進一步處理
        yield item

step6:定義StockItem類

class StockItem(scrapy.Item):
    # Scrapy 的 Item 類用於定義資料結構,所有要提取的欄位都在這裡定義

    xuHao = scrapy.Field()  # 序號,表示股票在頁面中的排列順序
    guPiaoDaiMa = scrapy.Field()  # 股票程式碼,用於唯一標識每隻股票
    guPiaoNingCheng = scrapy.Field()  # 股票名稱,顯示股票的名稱
    zuiXingBaoJia = scrapy.Field()  # 最新報價,表示股票的當前價格
    zhangDieFu = scrapy.Field()  # 漲跌幅,顯示股票價格變化的百分比
    zhangDieE = scrapy.Field()  # 漲跌額,表示股票價格上漲或下跌的金額
    chengJiaoLiang = scrapy.Field()  # 成交量,表示該股票在當前時段的交易量
    zhenFu = scrapy.Field()  # 振幅,表示股票價格波動的幅度
    zuiGao = scrapy.Field()  # 最高價,表示該股票在當前時段的最高交易價格
    zuiDi = scrapy.Field()  # 最低價,表示該股票在當前時段的最低交易價格
    jinKai = scrapy.Field()  # 今開,表示當天股票的開盤價
    zuoShou = scrapy.Field()  # 昨收,表示前一天股票的收盤價

step7:pipeline中,將資料存入mysql中

import mysql.connector  # 匯入 MySQL 聯結器,用於連線和運算元據庫
from itemadapter import ItemAdapter  # 匯入 Scrapy 的 ItemAdapter 類,方便操作爬取的 item 資料


class StockPipeline:
    count = 0  # 計數器,用於跟蹤資料處理的次數,確保只在第一次處理時建立資料庫表

    def open_spider(self, spider):
        """
        在爬蟲啟動時自動執行,負責連線資料庫和建立表。

        - 連線 MySQL 資料庫
        - 檢查是否是第一次執行爬蟲,如果是,則建立資料庫表
        """
        try:
            # 連線到 MySQL 資料庫
            self.con = mysql.connector.connect(
                host="localhost",  # MySQL 資料庫的主機地址
                user="root",  # MySQL 資料庫的使用者名稱
                password="123456",  # MySQL 資料庫的密碼
                database="DataAcquisition"  # 使用的資料庫名
            )
            # 建立一個資料庫遊標,用於執行 SQL 語句
            self.cursor = self.con.cursor()

            # 如果是第一次執行爬蟲(StockPipeline.count == 0),建立資料庫表
            if StockPipeline.count == 0:
                try:
                    # 如果已存在舊的表,刪除它(避免表重複)
                    self.cursor.execute("DROP TABLE IF EXISTS stock")
                    # 建立一個新的表結構
                    sql = """
                    CREATE TABLE IF NOT EXISTS stock (
                        xuHao VARCHAR(32) PRIMARY KEY,  # 股票編號,作為主鍵
                        guPiaoDaiMa VARCHAR(32),  # 股票程式碼
                        guPiaoNingCheng VARCHAR(32),  # 股票名稱
                        zuiXingBaoJia DECIMAL(10, 2),  # 最新報價,使用 DECIMAL 型別儲存帶小數的數值
                        zhangDieFu VARCHAR(32),  # 漲跌幅
                        zhangDieE DECIMAL(10, 2),  # 漲跌額,使用 DECIMAL 型別
                        chengJiaoLiang INT,  # 成交量,使用 INT 型別
                        zhenFu VARCHAR(32),  # 震幅
                        zuiGao DECIMAL(10, 2),  # 最高價,使用 DECIMAL 型別
                        zuiDi DECIMAL(10, 2),  # 最低價,使用 DECIMAL 型別
                        jinKai DECIMAL(10, 2),  # 今開,使用 DECIMAL 型別
                        zuoShou DECIMAL(10, 2)  # 昨收,使用 DECIMAL 型別
                    )
                    """
                    # 執行 SQL 語句建立表
                    self.cursor.execute(sql)
                except Exception as e:
                    # 如果表建立失敗,記錄錯誤資訊
                    spider.logger.error("Error creating table: %s", e)

        except Exception as err:
            # 如果資料庫連線失敗,記錄錯誤資訊
            spider.logger.error("Error connecting to MySQL: %s", err)

    def process_item(self, item, spider):
        """
        處理每個爬取的 item,負責將資料插入資料庫。

        - 從 item 中提取資料
        - 使用 SQL 插入資料到資料庫
        """
        try:
            # 準備插入資料的 SQL 語句
            sql = """
            INSERT INTO stock (xuHao, guPiaoDaiMa, guPiaoNingCheng, zuiXingBaoJia, zhangDieFu, zhangDieE, 
            chengJiaoLiang, zhenFu, zuiGao, zuiDi, jinKai, zuoShou)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            """

            # 從 item 中提取欄位值
            xuHao = item.get('xuHao', None)
            guPiaoDaiMa = item.get('guPiaoDaiMa', None)
            guPiaoNingCheng = item.get('guPiaoNingCheng', None)
            zuiXingBaoJia = item.get('zuiXingBaoJia', None)
            zhangDieFu = item.get('zhangDieFu', None)
            zhangDieE = item.get('zhangDieE', None)
            chengJiaoLiang = item.get('chengJiaoLiang', None)
            zhenFu = item.get('zhenFu', None)
            zuiGao = item.get('zuiGao', None)
            zuiDi = item.get('zuiDi', None)
            jinKai = item.get('jinKai', None)
            zuoShou = item.get('zuoShou', None)

            # 執行插入資料的 SQL 語句
            self.cursor.execute(sql, (
                xuHao, guPiaoDaiMa, guPiaoNingCheng, zuiXingBaoJia, zhangDieFu, zhangDieE,
                chengJiaoLiang, zhenFu, zuiGao, zuiDi, jinKai, zuoShou
            ))

            # 提交事務,儲存資料到資料庫
            self.con.commit()

        except Exception as err:
            # 如果插入資料失敗,記錄錯誤資訊
            spider.logger.error("Error inserting data: %s", err)

        # 返回 item,以便 Scrapy 繼續處理
        return item

    def close_spider(self, spider):
        """
        在爬蟲結束時關閉資料庫連線
        """
        try:
            # 關閉資料庫連線
            self.con.close()
        except Exception as err:
            # 如果關閉資料庫連線時出錯,記錄錯誤資訊
            spider.logger.error("Error closing MySQL connection: %s", err)

step8:執行程式碼

step9:得到結果

(二)心得

提升:做完後發現,好像還可以做一個翻頁的資料抓取,但是沒有時間做了,這個翻頁是ajax請求的,後面有空可以再繼續完善

本次股票資料爬取的難點主要是網頁的動態資料抓取,選擇好中介軟體,在middlewares.py中做相應的修改。在完成作業的過程中,逐漸掌握了 Scrapy 框架在處理動態網頁資料時的基本操作。從請求 URL 的構造、頁面解析到資料儲存,每一步都需要仔細思考和除錯。在 start_requests 方法中,針對不同頁面的 URL 進行請求時,我深刻理解了動態請求構建的重要性,尤其是在涉及 JavaScript 渲染內容時,如何有效地獲取頁面資料。

在資料解析部分,遇到了許多由於 JavaScript 渲染導致的資料缺失問題,這使我意識到傳統的 Scrapy 下載器無法滿足需求,因此我引入了 Selenium 作為中介軟體來處理動態頁面載入。這一步驟讓我學會了如何結合 Selenium 和 Scrapy 來獲取和處理動態網頁,尤其是如何利用 implicitly_wait 確保頁面完全載入。

parse 方法中,我進一步加深了對 XPath 的理解,能夠靈活地從網頁的複雜 HTML 結構中提取出準確的股票資料。定義 StockItem 類並將資料結構化,方便後續儲存時的使用。在資料儲存環節,透過使用 MySQL 聯結器,我學會了如何將爬取的資料儲存到資料庫中,並在爬蟲開始時建立表格結構,確保資料能夠持久化儲存。

整個專案讓我深刻體會到資料爬取過程中的挑戰,尤其是在面對動態網頁時的技術細節。透過解決爬取過程中的空值處理、動態頁面載入等問題,我積累了寶貴的經驗,也加深了對爬蟲技術的理解。

三、外匯網站資料爬取

碼雲倉庫:作業3/task3 · 曹星才/2022級資料採集與融合技術 - 碼雲 - 開源中國

(一)步驟

step1:找到對應的網站頁面,對不同頁面的url進行處理

# start_requests 方法用於生成初始請求併傳送到目標網站
def start_requests(self):
    pageNum = 2  # 設定要爬取的頁數

    # 迴圈遍歷頁面,構造每個頁面的 URL
    for page in range(0, pageNum):
        if page == 0:
            # 第一頁的 URL 結構不同,需要單獨處理
            url = "https://www.boc.cn/sourcedb/whpj/index.html"
        else:
            # 其他頁面的 URL 結構
            url = f"https://www.boc.cn/sourcedb/whpj/index_{page}.html"

        # 使用 scrapy.Request 傳送請求,並指定回撥函式 parse 來處理響應
        yield scrapy.Request(url=url, callback=self.parse)

step2:注意到網頁中有空值,後面要進行相應的處理

step3:解析頁面並提取資料

parse 方法解析網頁響應並提取外匯牌價資料。首先將響應位元組資料解碼為 UTF-8 字串,然後用 Scrapy 的 Selector 類解析 HTML 內容,定位到包含資料的表格。提取表格行,跳過表頭,從第三行開始逐行遍歷。對於每行,建立 ChinaBankItem 例項,使用 XPath 提取各列的資料:包括貨幣名稱、現匯買入價、現鈔買入價、現匯賣出價、現鈔賣出價和更新時間,將其存入 item 物件。最終,透過 yielditem 傳遞到管道進行進一步處理。

# parse 方法用於解析頁面響應並提取資料
def parse(self, response):
    data = response.body.decode('utf-8')  # 將響應的位元組資料解碼為 UTF-8 字串

    # 使用 Scrapy 的 Selector 類解析解碼後的 HTML 資料
    selector = scrapy.Selector(text=data)

    # 定位包含外匯牌價資料的表格,使用 XPath 表示式查詢
    body = selector.xpath("//div[@class='publish']//table")

    # 提取表格中的所有行(<tr> 元素)
    trs = body.xpath(".//tr")

    # 提取表頭資訊(第二行),並列印表頭內容用於除錯
    info_th = trs[1].xpath('.//th/text()').extract()
    print(info_th)

    # 遍歷所有行,從第三行開始(忽略表頭),提取每行的資料
    for info in trs[2:]:
        item = ChinaBankItem()  # 建立一個 ChinaBankItem 例項,用於儲存提取的資料

        # 使用 XPath 表示式提取每列的資料,並儲存到 item 對應的欄位中
        item["Currency"] = info.xpath("./td[1]/text()").get()  # 提取貨幣名稱
        item["TBP"] = info.xpath("./td[2]/text()").get()  # 提取現匯買入價
        item["CBP"] = info.xpath("./td[3]/text()").get()  # 提取現鈔買入價
        item["TSP"] = info.xpath("./td[4]/text()").get()  # 提取現匯賣出價
        item["CSP"] = info.xpath("./td[5]/text()").get()  # 提取現鈔賣出價
        item["Time"] = info.xpath("./td[7]/text()").get()  # 提取更新時間

        # 將提取的資料傳遞到 item 管道(pipelines)進行處理
        yield item

注:如果直接使用.extract(),就沒有辦法獲取空值,故這裡採用get

step4:定義 ChinaBankItem 類

# 定義一個 ChinaBankItem 類,用於儲存從網頁中提取的資料
class ChinaBankItem(scrapy.Item):
    # 定義用於儲存貨幣名稱的欄位
    Currency = scrapy.Field()

    # 定義用於儲存“現匯買入價”的欄位
    TBP = scrapy.Field()

    # 定義用於儲存“現鈔買入價”的欄位
    CBP = scrapy.Field()

    # 定義用於儲存“現匯賣出價”的欄位
    TSP = scrapy.Field()

    # 定義用於儲存“現鈔賣出價”的欄位
    CSP = scrapy.Field()

    # 定義用於儲存“更新時間”的欄位
    Time = scrapy.Field()

step5:mysql中建立資料庫

step6:在mysql資料庫建立bank表,並將爬取到的資料儲存到表中

# 定義一個處理從 Scrapy 爬蟲中提取資料的管道類
class ChinaBankPipeline:
    count = 0  # 定義一個類變數 count,用於跟蹤處理的資料項數

    # 處理每個傳入的 item 物件
    def process_item(self, item, spider):
        try:
            # 連線到 MySQL 資料庫(請根據實際情況修改為你的資料庫配置)
            con = mysql.connector.connect(
                host="localhost",  # 資料庫主機地址
                user="root",  # 資料庫使用者名稱
                password="123456",  # 資料庫密碼
                database="DataAcquisition"  # 目標資料庫名稱
            )
            cursor = con.cursor()  # 建立一個遊標物件,用於執行 SQL 語句

            # 如果這是第一個被處理的 item,則建立表結構
            if ChinaBankPipeline.count == 0:
                try:
                    # 刪除舊錶(如果已存在)以避免衝突
                    cursor.execute("DROP TABLE IF EXISTS bank")

                    # 建立新的表結構
                    sql = """
                    CREATE TABLE IF NOT EXISTS bank (
                        Currency VARCHAR(64) PRIMARY KEY,  # 貨幣資訊,作為主鍵
                        TBP FLOAT,  # 現匯買入價
                        CBP FLOAT,  # 現鈔買入價
                        TSP FLOAT,  # 現匯賣出價
                        CSP FLOAT,  # 現鈔賣出價
                        Time VARCHAR(64)  # 更新時間
                    )
                    """
                    cursor.execute(sql)  # 執行 SQL 語句建立表
                except Exception as e:
                    print("Error creating table:", e)  # 如果出錯,列印錯誤資訊

            # 準備插入資料的 SQL 語句
            sql = """
            INSERT INTO bank (Currency, TBP, CBP, TSP, CSP, Time)
            VALUES (%s, %s, %s, %s, %s, %s)
            """
            try:
                # 從 item 物件中提取資料
                currency = item.get('Currency')  # 獲取貨幣資訊
                tbp = item.get('TBP')  # 獲取現匯買入價
                cbp = item.get('CBP')  # 獲取現鈔買入價
                tsp = item.get('TSP')  # 獲取現匯賣出價
                csp = item.get('CSP')  # 獲取現鈔賣出價
                time = item.get('Time')  # 獲取更新時間

                # 執行 SQL 插入語句,將資料插入資料庫
                cursor.execute(sql, (currency, tbp, cbp, tsp, csp, time))
            except Exception as err:
                print("Error inserting data:", err)  # 如果插入資料時出錯,列印錯誤資訊

            # 提交事務,儲存資料更改
            con.commit()
            # 關閉資料庫連線
            con.close()

        except Exception as err:
            print("Error:", err)  # 如果連線資料庫或其他操作失敗,列印錯誤資訊

        # 增加計數器,表示已處理的資料項數
        ChinaBankPipeline.count += 1

        # 返回處理後的 item 物件
        return item

step7scrapy crawl wea -s LOG_ENABLED=False執行專案

step8:mysql中檢視結果

(二)心得

在完成這次外匯網站資料爬取的過程中,逐漸掌握了使用 Scrapy 框架獲取網頁資料的基本流程。從構造 URL 請求、解析網頁到將資料儲存在資料庫中,每一步都需要細心和耐心。在 start_requests 方法中,處理不同頁面的 URL 讓我意識到構建動態請求的重要性。解析資料時,我注意到網頁中可能會有空值,因此學習並應用了 .get() 方法來避免資料缺失問題,這顯著提高了程式碼的穩定性和健壯性。

在提取資料的 parse 方法中,進一步加深了對 XPath 語法的理解,能高效地從複雜的 HTML 結構中定位和提取資訊。定義 ChinaBankItem 類明確了資料結構,為後續的儲存提供了便利。在儲存資料環節,學會了使用 MySQL 連線和操作,理解了如何在管道中處理資料,建立表結構,並插入爬取的資料。

整個專案讓我體會到資料爬取和處理的過程,專案中遇到的空值處理、資料庫連線等問題,逐步慢慢的都解決了。

相關文章