2024年7月19日 星期五

Python 學習筆記 : 網頁爬蟲框架 Scrapy 用法 (四)

在前面的測試中我們用 Scrapy 製作了一個網頁爬蟲來抓取台銀牌告匯率網站的資料, 但只簡單地將擷取到的目標資料以字典方式傳回或輸出, 雖然這樣就能完成基本的爬蟲任務, 但並沒有充分利用 Scrapy 專案系統架構的優勢. 因為網頁提供的是非結構性資料, 網頁爬蟲除了擷取資料外, 還必須將其轉成結構化資料儲存於資料庫, 因為結構化資料最適合進行統計分析, 大多數的資料科學分析都是在結構化資料上完成的. 

在前面的 Scrapy 測試中透過 -o 選項可將擷取到的目標資料 (字典) 儲存 json 檔, 雖然字典或 JSON 資料也算是一種結構性資料, 但是在程式中無法清楚看出資料欄位而影響可讀性, 此外字典或 JSON 資料缺乏欄名之檢查容易發生錯誤, 因此 Scrapy 架構提供了 Item 物件來封裝資料欄位, 並與管線 (pipelines) 結合使得儲存到關聯式資料庫時 (例如 SQLite) 更方便. 

本系列之前的筆記參考 : 



九. 使用 Item 物件定義結構化資料項目 :

Scrapy 的結構化資料封裝主要是透過 Item 與 Field 類別, Item 用來定義資料項目, 而 Field 則用來定義資料欄位, 在建立專案時, 於第二層專案目錄下的 items.py 檔就是用來定義結構化資料欄位的, 本篇仍以爬取台灣銀行外匯牌告利率網頁為例說明如何使用 Scrapy 的 Item 與 Field 封裝結構化資料項目. 

首先建立一個新專案 project3 :

scrapy startproject project3

D:\python\test\scrapy_projects>scrapy startproject project3    
New Scrapy project 'project3', using template directory 'C:\Users\tony1\AppData\Local\Programs\Thonny\Lib\site-packages\scrapy\templates\project', created in:
    D:\python\test\scrapy_projects\project3

You can start your first spider with:
    cd project3
    scrapy genspider example example.com

建立好專案後, 在第二層專案目錄 project3 下即有一個 items.py 檔 : 

D:\python\test\scrapy_projects>cd project3   

D:\python\test\scrapy_projects\project3>tree project3 /f     
列出磁碟區 新增磁碟區 的資料夾 PATH
磁碟區序號為 1258-16B8
D:\PYTHON\TEST\SCRAPY_PROJECTS\PROJECT3\PROJECT3
│  items.py    
│  middlewares.py
│  pipelines.py
│  settings.py
│  __init__.py
└─spiders
        __init__.py

items.py 的預設內容如下 : 

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy

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

可見預設只是一個繼承 scrapy.Item 的空子類別定義了一個預設名稱為 Project3Item 的類別, 其中 Project3 是專案名稱. 類別名稱是可自訂的, 此例可以改為 RateItem. 

要將爬蟲擷取之資料封裝到結構化的資料項目需編輯 items.py, 利用 scrapy.Item 與 scrapy.Field 類別定義資料項目的各個欄位. 以台銀牌告的 19 種貨幣來說就是定義 19 個欄位. 在前面的測試中已知這 19 個幣別串列 currency 如下 :

>>> print(currency)  
['美金 (USD)', '港幣 (HKD)', '英鎊 (GBP)', '澳幣 (AUD)', '加拿大幣 (CAD)', '新加坡幣 (SGD)', '瑞士法郎 (CHF)', '日圓 (JPY)', '南非幣 (ZAR)', '瑞典幣 (SEK)', '紐元 (NZD)', '泰幣 (THB)', '菲國比索 (PHP)', '印尼幣 (IDR)', '歐元 (EUR)', '韓元 (KRW)', '越南盾 (VND)', '馬來幣 (MYR)', '人民幣 (CNY)'] 

而匯率串列例如 :

>>> print(rate)  
['32.895', '4.229', '43.24', '22.35', '24.27', '24.61', '36.8', '0.2093', '-', '3.21', '20.13', '0.9657', '0.6246', '0.00238', '36.09', '0.02573', '0.00145', '7.477', '4.542']

要以結構化方式儲存這 19 個匯率項目可於 items.py 中定義兩個欄位 currency 與 rate. 做法是在 items.py 中繼承 Items 類別, 並用 Field 類別定義兩個資料欄位 currency 與 rate : 

# items.py
import scrapy

class RateItem(scrapy.Item):
    currency=scrapy.Field()   # 定義儲存幣別之欄位
    rate=scrapy.Field()           # 定義儲存匯率之欄位

第二種寫法如下 :

# items.py
from scrapy import Item, Field

class RateItem(Item):
    currency=Field()   # 定義儲存幣別之欄位
    rate=Field()           # 定義儲存匯率之欄位

先在互動環境測試 Item 物件用法 : 

>>> import scrapy   
>>> class RateItem(scrapy.Item):    # 定義資料項目 Item 之子類別
    currency=scrapy.Field()     
    rate=scrapy.Field()     

呼叫建構式建立資料項目物件實體並初始化 :

>>> rate1=RateItem()      # 建立資料項目實體 (物件) 
>>> type(rate1)   
<class '__main__.RateItem'>   
>>> rate1['currency']='美金 (USD)'     
>>> rate1['rate']='32.895'     
>>> rate1     
{'currency': '美金 (USD)', 'rate': '32.895'}   

看起來跟字典沒兩樣, 用 dir() 檢視這個 RateItem 物件內容 :
 
>>> dir(rate1)   
['_MutableMapping__marker', '__abstractmethods__', '__annotations__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__setitem__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', '_class', '_values', 'clear', 'copy', 'deepcopy', 'fields', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

可見 Item 物件其實就是一個字典物件, 其成員與字典完全相同, 例如 :

>>> rate1.keys()   
dict_keys(['currency', 'rate']) 
>>> rate1.values()   
ValuesView({'currency': '美金 (USD)', 'rate': '32.895'})  
>>> rate1.items()   
ItemsView({'currency': '美金 (USD)', 'rate': '32.895'})    
>>> rate1.get('currency')   
'美金 (USD)'
>>> rate1.get('rate')   
'32.895'

定義好 RateItem 類別後, 即可在爬蟲程式中將其實例化以封裝擷取到的目標資料. 

在第一層專案目錄 project3 底下用 genspider 指令建立爬蟲程式 bot_rate_spider.py :

scrapy genspider bot_rate_spider rate.bot.com.tw   

此處指定 bot_rate_spider.py 為 spiders 目錄下之爬蟲程式檔案名稱 (主檔名即可), rate.bot.com.tw 為台銀牌告匯率網站之網域 :

D:\python\test\scrapy_projects\project3>scrapy genspider bot_rate_spider rate.bot.com.tw    
Created spider 'bot_rate_spider' using template 'basic' in module:
  project3.spiders.bot_rate_spider

這指令會用預設的 basic 模板在 spiders 目錄下自動生成爬蟲程式檔 bot_rate_spider.py :

D:\python\test\scrapy_projects\project3>tree project3 /f   
列出磁碟區 新增磁碟區 的資料夾 PATH
磁碟區序號為 1258-16B8
D:\PYTHON\TEST\SCRAPY_PROJECTS\PROJECT3\PROJECT3
│  items.py
│  middlewares.py
│  pipelines.py
│  settings.py
│  __init__.py
├─spiders
│  │  bot_rate_spider.py   
│  │  __init__.py
│  │
│  └─__pycache__
│          __init__.cpython-310.pyc
└─__pycache__
        settings.cpython-310.pyc
        __init__.cpython-310.pyc

不過在修改爬蟲程式 bot_rate_spider.py 之前, 我們先在互動環境測試如何將爬取到的目標資料存入 Item 物件中. 

下面為在前一篇測試中爬取到的 currency 與 rate 串列 :

>>> currency=['美金 (USD)', '港幣 (HKD)', '英鎊 (GBP)', '澳幣 (AUD)', '加拿大幣 (CAD)', '新加坡幣 (SGD)', '瑞士法郎 (CHF)', '日圓 (JPY)', '南非幣 (ZAR)', '瑞典幣 (SEK)', '紐元 (NZD)', '泰幣 (THB)', '菲國比索 (PHP)', '印尼幣 (IDR)', '歐元 (EUR)', '韓元 (KRW)', '越南盾 (VND)', '馬來幣 (MYR)', '人民幣 (CNY)']    
>>> rate=['32.895', '4.229', '43.24', '22.35', '24.27', '24.61', '36.8', '0.2093', '-', '3.21', '20.13', '0.9657', '0.6246', '0.00238', '36.09', '0.02573', '0.00145', '7.477', '4.542']    

可用 zip() 將這兩個串列對應的元素綁定為鍵值對形成一個字典, 這樣便能在迴圈中同步地走訪兩個串列的內容 :
  
>>> rate_dict={c: r for c, r in zip(currency, rate)}   
>>> rate_dict    
{'美金 (USD)': '32.895', '港幣 (HKD)': '4.229', '英鎊 (GBP)': '43.24', '澳幣 (AUD)': '22.35', '加拿大幣 (CAD)': '24.27', '新加坡幣 (SGD)': '24.61', '瑞士法郎 (CHF)': '36.8', '日圓 (JPY)': '0.2093', '南非幣 (ZAR)': '-', '瑞典幣 (SEK)': '3.21', '紐元 (NZD)': '20.13', '泰幣 (THB)': '0.9657', '菲國比索 (PHP)': '0.6246', '印尼幣 (IDR)': '0.00238', '歐元 (EUR)': '36.09', '韓元 (KRW)': '0.02573', '越南盾 (VND)': '0.00145', '馬來幣 (MYR)': '7.477', '人民幣 (CNY)': '4.542'}   

走訪字典物件的每個項目時可以用字典物件的 items() 方法拆開項目中的鍵 (幣別 currency) 與值 (匯率 rate), 方便寫入資料項目的不同欄位中 :

>>> for c, r in rate_dict.items():   
    print(c, r)     
    
美金 (USD) 32.895
港幣 (HKD) 4.229
英鎊 (GBP) 43.24
澳幣 (AUD) 22.35
加拿大幣 (CAD) 24.27
新加坡幣 (SGD) 24.61
瑞士法郎 (CHF) 36.8
日圓 (JPY) 0.2093
南非幣 (ZAR) -
瑞典幣 (SEK) 3.21
紐元 (NZD) 20.13
泰幣 (THB) 0.9657
菲國比索 (PHP) 0.6246
印尼幣 (IDR) 0.00238
歐元 (EUR) 36.09
韓元 (KRW) 0.02573
越南盾 (VND) 0.00145
馬來幣 (MYR) 7.477
人民幣 (CNY) 4.542

這樣我們就可以將拆出的 c 與 r 填入 RateItem 物件的 currency 與 rate 欄位中了 : 

>>> for c, r in rate_dict.items():    
  rate_item=RateItem()     
  rate_item['currency']=c     
  rate_item['rate']=r     
  print(rate_item)      
  
{'currency': '美金 (USD)', 'rate': '32.895'}
{'currency': '港幣 (HKD)', 'rate': '4.229'}
{'currency': '英鎊 (GBP)', 'rate': '43.24'}
{'currency': '澳幣 (AUD)', 'rate': '22.35'}
{'currency': '加拿大幣 (CAD)', 'rate': '24.27'}
{'currency': '新加坡幣 (SGD)', 'rate': '24.61'}
{'currency': '瑞士法郎 (CHF)', 'rate': '36.8'}
{'currency': '日圓 (JPY)', 'rate': '0.2093'}
{'currency': '南非幣 (ZAR)', 'rate': '-'}
{'currency': '瑞典幣 (SEK)', 'rate': '3.21'}
{'currency': '紐元 (NZD)', 'rate': '20.13'}
{'currency': '泰幣 (THB)', 'rate': '0.9657'}
{'currency': '菲國比索 (PHP)', 'rate': '0.6246'}
{'currency': '印尼幣 (IDR)', 'rate': '0.00238'}
{'currency': '歐元 (EUR)', 'rate': '36.09'}
{'currency': '韓元 (KRW)', 'rate': '0.02573'}
{'currency': '越南盾 (VND)', 'rate': '0.00145'}
{'currency': '馬來幣 (MYR)', 'rate': '7.477'}
{'currency': '人民幣 (CNY)', 'rate': '4.542'}

最後依據上面的測試結果來修改爬蟲程式如下 : 

# bot_rate_spider.py 
import scrapy
from project3.items import RateItem   # 也可以用 ..items 

class RateSpider(scrapy.Spider):
    name='bot_rate_spider'
    allowed_domains=['rate.bot.com.tw']
    start_urls=['https://rate.bot.com.tw/xrt?Lang=zh-TW']

    def parse(self, response):
        xpath='//tbody/tr/td/div/div[position()=2]/text()'  
        currency=response.xpath(xpath).getall()    
        currency=[c.strip() for c in currency]       
        xpath='//tbody/tr/td[position()=3]/text()'    
        rate=response.xpath(xpath).getall()    
        rate_dict={c: r for c, r in zip(currency, rate)}
        for c, r in rate_dict.items():    # 走訪
            rate_item=RateItem()      # 建立資料項目物件
            rate_item['currency']=c    # 儲存幣別
            rate_item['rate']=r     # 儲存匯率
            yield rate_item      # 傳回資料項目物件

此處匯入 items.py 中的 RateItem 類別時要注意路徑是否正確, 因為爬蟲程式位於 spiders 目錄下, 故必須用 .. 往上跳一層才能找到 items.py; 當然也可以直接指定第二層專案目錄 project3. 

執行爬蟲程式結果如下 : 

scrapy crawl bot_rate_spider -o data.json  

D:\python\test\scrapy_projects\project3>scrapy crawl bot_rate_spider -o data.json    
2024-07-19 11:20:26 [scrapy.utils.log] INFO: Scrapy 2.11.2 started (bot: project3)
2024-07-19 11:20:26 [scrapy.utils.log] INFO: Versions: lxml 4.9.3.0, libxml2 2.10.3, cssselect 1.2.0, parsel 1.9.1, w3lib 2.2.1, Twisted 24.3.0, Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)], pyOpenSSL 24.1.0 (OpenSSL 3.2.2 4 Jun 2024), cryptography 42.0.8, Platform Windows-10-10.0.22631-SP0
2024-07-19 11:20:26 [scrapy.addons] INFO: Enabled addons:
[]
2024-07-19 11:20:26 [asyncio] DEBUG: Using selector: SelectSelector
2024-07-19 11:20:26 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor
2024-07-19 11:20:26 [scrapy.utils.log] DEBUG: Using asyncio event loop: asyncio.windows_events._WindowsSelectorEventLoop
2024-07-19 11:20:26 [scrapy.extensions.telnet] INFO: Telnet Password: ba80414fe81344b3
2024-07-19 11:20:26 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.feedexport.FeedExporter',
 'scrapy.extensions.logstats.LogStats']
2024-07-19 11:20:26 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'project3',
 'FEED_EXPORT_ENCODING': 'utf-8',
 'NEWSPIDER_MODULE': 'project3.spiders',
 'REQUEST_FINGERPRINTER_IMPLEMENTATION': '2.7',
 'ROBOTSTXT_OBEY': True,
 'SPIDER_MODULES': ['project3.spiders'],
 'TWISTED_REACTOR': 'twisted.internet.asyncioreactor.AsyncioSelectorReactor'}
2024-07-19 11:20:26 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2024-07-19 11:20:26 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2024-07-19 11:20:26 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2024-07-19 11:20:26 [scrapy.core.engine] INFO: Spider opened
2024-07-19 11:20:26 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2024-07-19 11:20:26 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2024-07-19 11:20:26 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://rate.bot.com.tw/robots.txt> (referer: None)
2024-07-19 11:20:26 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://rate.bot.com.tw/xrt?Lang=zh-TW> (referer: None)
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '美金 (USD)', 'rate': '33.025'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '港幣 (HKD)', 'rate': '4.241'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '英鎊 (GBP)', 'rate': '43.32'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '澳幣 (AUD)', 'rate': '22.34'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '加拿大幣 (CAD)', 'rate': '24.31'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '新加坡幣 (SGD)', 'rate': '24.7'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '瑞士法郎 (CHF)', 'rate': '37.24'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '日圓 (JPY)', 'rate': '0.2116'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '南非幣 (ZAR)', 'rate': '-'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '瑞典幣 (SEK)', 'rate': '3.22'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '紐元 (NZD)', 'rate': '20.13'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '泰幣 (THB)', 'rate': '0.966'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '菲國比索 (PHP)', 'rate': '0.6276'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '印尼幣 (IDR)', 'rate': '0.00238'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '歐元 (EUR)', 'rate': '36.19'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '韓元 (KRW)', 'rate': '0.02577'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '越南盾 (VND)', 'rate': '0.00148'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '馬來幣 (MYR)', 'rate': '7.504'}
2024-07-19 11:20:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://rate.bot.com.tw/xrt?Lang=zh-TW>
{'currency': '人民幣 (CNY)', 'rate': '4.563'}
2024-07-19 11:20:26 [scrapy.core.engine] INFO: Closing spider (finished)
2024-07-19 11:20:26 [scrapy.extensions.feedexport] INFO: Stored json feed (19 items) in: data.json
2024-07-19 11:20:26 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 745,
 'downloader/request_count': 2,
 'downloader/request_method_count/GET': 2,
 'downloader/response_bytes': 137685,
 'downloader/response_count': 2,
 'downloader/response_status_count/200': 2,
 'elapsed_time_seconds': 0.526893,
 'feedexport/success_count/FileFeedStorage': 1,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2024, 7, 19, 3, 20, 26, 936400, tzinfo=datetime.timezone.utc),
 'item_scraped_count': 19,
 'log_count/DEBUG': 24,
 'log_count/INFO': 11,
 'response_received_count': 2,
 'robotstxt/request_count': 1,
 'robotstxt/response_count': 1,
 'robotstxt/response_status_count/200': 1,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 1,
 'scheduler/enqueued/memory': 1,
 'start_time': datetime.datetime(2024, 7, 19, 3, 20, 26, 409507, tzinfo=datetime.timezone.utc)}
2024-07-19 11:20:26 [scrapy.core.engine] INFO: Spider closed (finished)

開啟輸出檔 data.json 內容如下 :

[
{"currency": "美金 (USD)", "rate": "33.025"},
{"currency": "港幣 (HKD)", "rate": "4.241"},
{"currency": "英鎊 (GBP)", "rate": "43.32"},
{"currency": "澳幣 (AUD)", "rate": "22.34"},
{"currency": "加拿大幣 (CAD)", "rate": "24.31"},
{"currency": "新加坡幣 (SGD)", "rate": "24.7"},
{"currency": "瑞士法郎 (CHF)", "rate": "37.24"},
{"currency": "日圓 (JPY)", "rate": "0.2116"},
{"currency": "南非幣 (ZAR)", "rate": "-"},
{"currency": "瑞典幣 (SEK)", "rate": "3.22"},
{"currency": "紐元 (NZD)", "rate": "20.13"},
{"currency": "泰幣 (THB)", "rate": "0.966"},
{"currency": "菲國比索 (PHP)", "rate": "0.6276"},
{"currency": "印尼幣 (IDR)", "rate": "0.00238"},
{"currency": "歐元 (EUR)", "rate": "36.19"},
{"currency": "韓元 (KRW)", "rate": "0.02577"},
{"currency": "越南盾 (VND)", "rate": "0.00148"},
{"currency": "馬來幣 (MYR)", "rate": "7.504"},
{"currency": "人民幣 (CNY)", "rate": "4.563"}
]

可見目標資料在 Item 物件封裝下已轉成結構化資料了. 與前幾篇單純地輸出字典不同的是, Item 物件每個項目相當於一列資料, 每列都有欄位名稱, 在儲存至具有固定欄位的關聯式資料庫時欄位名卻更不容易出錯.  

以上 project3 專案的壓縮檔可從 GitHub 下載 : 


沒有留言 :