這個作業屬於哪個課程 | 2024資料採集與融合技術實踐 |
---|---|
這個作業要求在哪裡 | 作業3 |
這個作業的目標 | 1.爬蟲設計 2.掌握scrapy爬蟲框架,並實際應用 3.掌握scrapy爬取動態頁面(實現scrapy與selenium的對接) |
學號 | 102202123 |
Gitee🔗:作業3
作業整體思路:
- 建立一個scrapy專案(scrapy startproject scrapy_name)
- 新建一個爬蟲(scrapy genspider spider_name spider_domain)
- items.py(需要爬取的資料項)
- 爬蟲專案修改(頁面url變化、解析響應邏輯、爬取資料)
- piplines.py(下載、儲存)
- settings.py(以下是一些配置說明👇)
- 最大併發請求下載數量CONCURRENT_REQUESTS
- 資料管道ITEM_PIPELINES
- 圖片下載地址IMAGES_STORE 實驗3.1
- 下載路徑DEFAULT_IMAGES_URLS_FIELD
- 本地儲存路徑DEFAULT_IMAGES_RESULT_FIELD
- 注:這些都在items有定義
- 自動延遲AUTOTHROTTLE 溫和模擬模擬爬取網站
- 是否遵循爬蟲協議ROBOTSTXT_OBEY 本次作業通常為False,不然沒有許可權爬下來x
- 其他預設建立專案即配置(專案名稱、爬蟲模組定義等)
注:由於此處標明瞭具體過程,部落格撰寫重心為關鍵程式碼實現思路
作業①
要求:指定一個網站,爬取這個網站中的所有的所有圖片,例如:中國氣象網(http://www.weather.com.cn)。使用scrapy框架分別實現單執行緒和多執行緒的方式爬取。
務必控制總頁數(學號尾數2位)、總下載的圖片數量(尾數後3位)等限制爬取的措施。
輸出資訊: 將下載的Url資訊在控制檯輸出,並將下載的圖片儲存在images子檔案中,並給出截圖。
詳細程式碼見gitee🔗:作業3-實驗3.1
實踐過程
1.items.py
最終目標需要下載圖片,所以這裡定義了兩個資料專案類:
- 爬取網站圖片url
- 最後下載圖片本地路徑
class WeatherImageItem(scrapy.Item):
image_urls = scrapy.Field() # 用於儲存圖片的 URL 列表
images = scrapy.Field() # 用於儲存下載後的圖片資訊
2.修改爬蟲檔案
①檢測爬取網站
若直接爬取題目中所指的網站,檢測ELEMENT可知:
- 元素節點混亂,不統一
- 不能滿足題目的翻頁
- 圖片數不可能達到學號後三位
自己摸索找到了天氣網-圖片這個網站
url檢查:
- index會隨翻頁改變
- 中間有欄位是你選擇模組的拼音縮寫
for page in range(start, end + 1):
if page == 1:
url = f'https://p.weather.com.cn/{block}/index.shtml'
yield scrapy.Request(url=url, callback=self.parse)
else:
url = f'https://p.weather.com.cn/{block}/index_{page}.shtml'
yield scrapy.Request(url=url, callback=self.parse)
網頁檢查:
pictures = response.xpath('//div[@class="oi"]')
item = WeatherImageItem()
url = picture.xpath('.//div[@class="tu"]/a/img/@src').extract()[0] # 提取圖片連結
item["image_urls"] = [url]
②設定最大爬取數
def __init__(self, max_items=None, *args, **kwargs):
super(WeatherSpider, self).__init__(*args, **kwargs)
self.max_items = int(max_items) if max_items else None
self.item_count = 0 # 引入計數器,每提取一個圖片路徑就+1,直到達到max——items
同時在迴圈獲取圖片下載路徑時,要判別item_count的大小,達到最大就截斷:
for picture in pictures:
if self.max_items and self.item_count >= self.max_items:
self.log(f"Reached maximum items limit: {self.max_items}", level=scrapy.log.DEBUG)
print(f"Reached maximum items limit: {self.max_items}")
return
3.piplines.py
管道類本身帶有下載圖片的功能,這裡使用了內建管道ImagesPipeline
來處理和下載圖片
- 圖片 URL 的提取: 它能夠從 item 中提取圖片 URL,併為每個 URL 傳送下載請求。
- 圖片儲存: 下載的圖片會按照配置的儲存路徑儲存到本地。
- 下載結果的處理: 下載完成後,可以在
item_completed
方法中處理下載結果。 - 下載結果預設存在{指定路徑}/full/下
from scrapy.pipelines.images import ImagesPipeline
class WeatherImagesPipeline(ImagesPipeline):
def item_completed(self, results, item, info):
for ok, result in results:
if ok:
image_url = result['url']
print(f"Downloaded image: {image_url}")
return item
4.settings.py
主要解釋標題下的配置說明
IMAGES_STORE = 'images' # 儲存圖片的目錄
DEFAULT_IMAGES_URLS_FIELD = 'image_urls' # urls存放路徑
DEFAULT_IMAGES_RESULT_FIELD = 'images' # 下載後圖片所處本地路徑
CONCURRENT_REQUESTS = 16 # 設定併發請求數
題目要求單執行緒和多執行緒分別編寫
在scrapy中體現為併發請求數的設定,即CONCURRENT_REQUESTS
- 單執行緒爬取:將 CONCURRENT_REQUESTS 設定為 1,這意味著 Scrapy 只會在任何時刻處理一個請求,儘管它仍然是非同步的。
- 多執行緒爬取:將 CONCURRENT_REQUESTS 設定為更高的值(例如 16),這樣 Scrapy 將同時處理多個請求,儘管它仍然是在非同步環境中。
實踐結果
本次實踐爬取的是天氣現場的2-5頁
實踐心得
這次實驗是我自己在天氣網裡面摸索找到這個模組進行作業的,好像更符合題意,但美中不足的是,我檢查了所有模組,這個網站圖片數量比較少,都沒法爬下我學號後三位那麼多圖片,這次沒有直接截停圖片下載的體驗。
我是做完所有作業再來寫部落格,我考慮了一下,可能用selenium,模擬使用者點選網站,就能進一步爬取更多圖片了。在天氣網-圖片中,我選取的模組頁面只將一張圖片作為封面,其實點進去,還有多張圖片,如果能動態模擬點選並進行下一頁翻頁,可以獲得更多圖片。
從這題我也瞭解到了scrapy管道類的內建方法ImagesPipeline
,用於高效下載圖片,它有很完善的框架,不用像先前還需要自己寫下載圖片的程式碼。
作業②
要求:熟練掌握 scrapy 中 Item、Pipeline 資料的序列化輸出方法;使用scrapy框架+Xpath+MySQL資料庫儲存技術路線爬取股票相關資訊。
候選網站:東方財富網:https://www.eastmoney.com/
輸出資訊:MySQL資料庫儲存和輸出:序號、股票程式碼、股票名稱、最新報價、漲跌幅、漲跌額、成交量、振幅、最高、最低、今開、昨收,表頭英文命名
詳細程式碼見gitee🔗:作業3-實驗3.2
實踐過程
1.實現scrapy與selenium對接
由於東方財富網的行情模組是動態渲染的,所以需要完成以上對接才能成功爬取資料
def __init__(self):
self.driver = webdriver.Chrome() # 使用 Chrome 瀏覽器
self.max_stocks = 123 # 最大爬取數量
self.current_count = 0 # 當前已爬取數量
super().__init__()
def parse(self, response):
self.driver.get(response.url) # 開啟網站
time.sleep(2) # 等待頁面載入
包括後面Selector型別也是透過selenium驅動獲得網頁原始碼
sel = Selector(text=self.driver.page_source)
# ...(資料爬取在第二點)
爬取完畢也要退出瀏覽器
def closed(self, reason):
self.driver.quit() # 確保退出瀏覽器
2.網頁檢查與資料爬取
所有資料都在元素對裡面,
再觀察網頁可以注意到,部分帶連結的資料項會巢狀在a元素中,帶顏色(紅表示漲,綠表示跌了)在span裡面
stocks = sel.xpath('//table/tbody//tr') # 選擇股票資料的容器
self.logger.info(f"Found {len(stocks)} stocks.") # 輸出找到的股票數量
for stock in stocks:
if self.current_count >= self.max_stocks:
break # 達到最大爬取數量,停止爬取
item = StockItem()
try:
item['stock_id'] = stock.xpath('./td[1]/text()').get() # 序號
item['stock_code'] = stock.xpath('./td[2]/a/text()').get() # 股票程式碼
item['stock_name'] = stock.xpath('./td[3]/a/text()').get() # 股票名稱
item['latest_price'] = stock.xpath('./td[5]/span/text()').get() # 最新報價
item['change_percent'] = stock.xpath('./td[6]/span/text()').get() # 漲跌幅
item['change_amount'] = stock.xpath('./td[7]/span/text()').get() # 漲跌額
item['volume'] = stock.xpath('./td[8]/text()').get() # 成交量
item['amplitude'] = stock.xpath('./td[10]/text()').get() # 振幅
item['high'] = stock.xpath('./td[11]/span/text()').get() # 最高
item['low'] = stock.xpath('./td[12]/span/text()').get() # 最低
item['open'] = stock.xpath('./td[13]/span/text()').get() # 今開
item['prev_close'] = stock.xpath('./td[14]/text()').get() # 昨收
3.翻頁爬取
翻頁觀察發現,url並不會改變
所以這裡用selenium模擬使用者使用瀏覽器點選下一頁翻頁,達成多頁股票資料爬取
# 查詢並點選“下一頁”按鈕
try:
next_button = self.driver.find_element("link text", "下一頁")
if "disabled" in next_button.get_attribute("class"):
break # 如果“下一頁”按鈕被禁用,停止翻頁
next_button.click()
time.sleep(2) # 等待頁面載入
except Exception as e:
self.logger.error(f"Error clicking next page: {e}")
break # 如果出現錯誤,停止翻頁
4.連線並將資料儲存在MySQL
這裡已經在編輯資料管道類
思路
- 連線資料庫(資料庫使用者登入、要連線的提前建立好的資料庫)
- 建立表(create)
- 插入資料(insert into)
- 關閉資料庫
建立表程式碼
def create_table(self):
# 建立股票資訊表
create_table_sql = """
CREATE TABLE IF NOT EXISTS stocks (
stock_id VARCHAR(10) PRIMARY KEY,
stock_code VARCHAR(10) NOT NULL,
stock_name VARCHAR(50) NOT NULL,
latest_price REAL,
change_percent REAL,
change_amount REAL,
volume REAL,
amplitude REAL,
high REAL,
low REAL,
open REAL,
prev_close REAL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
try:
self.cursor.execute(create_table_sql)
self.connection.commit()
print("Table 'stocks' created successfully.")
except pymysql.MySQLError as e:
print(f"Error creating table: {e}")
self.connection.rollback() # 出現錯誤時回滾
插入資料程式碼
```python sql = """ INSERT INTO stocks (stock_id, stock_code, stock_name, latest_price, change_percent, change_amount, volume, amplitude, high, low, open, prev_close) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ try: spider.logger.info(f"Inserting data: {item}") self.cursor.execute(sql, ( item['stock_id'], item['stock_code'], item['stock_name'], item['latest_price'], item['change_percent'], item['change_amount'], item['volume'], item['amplitude'], item['high'], item['low'], item['open'], item['prev_close'] )) self.connection.commit() spider.logger.info("Data inserted successfully.") except pymysql.MySQLError as e: spider.logger.error(f"Error inserting data: {e}") self.connection.rollback() # 出現錯誤時回滾 except Exception as e: spider.logger.error(f"Unexpected error: {e}") ```5.資料清洗
因為MySQL資料庫資料型別限制,例如漲跌幅的百分號和成交量的萬,都需要進行展開轉換
try:
item['latest_price'] = float(item['latest_price'])
item['change_percent'] = float(item['change_percent'].replace('%', '').strip()) / 100.0 # 轉換為小數
item['change_amount'] = float(item['change_amount'])
# 處理 volume,將萬轉換為實際數值
item['volume'] = float(item['volume'].replace('萬', '').strip()) * 10000
# 處理 amplitude,假設其值是百分比形式,去掉百分號並轉換
item['amplitude'] = float(item['amplitude'].replace('%', '').strip()) / 100.0 if item['amplitude'] else 0.0
# 處理其他欄位
item['high'] = float(item['high']) if item['high'] else 0.0
item['low'] = float(item['low']) if item['low'] else 0.0
item['open'] = float(item['open']) if item['open'] else 0.0
item['prev_close'] = float(item['prev_close']) if item['prev_close'] else 0.0
except ValueError as ve:
spider.logger.error(f"Value conversion error: {ve}")
return item # 如果出現錯誤,可以選擇跳過該條目
6.settings設定
這裡爬取必須進行以下設定,不然沒法爬
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
實踐結果
實踐心得
這題是在老師的提示下才能完成的。在此之前我也沒想到要動態爬取網頁資訊,要讓scrapy和selenium對接。
針對此題url的特殊性,我提前應用selenium的方式實現了翻頁爬取資料操作,成功爬下了123項股票資料,收穫頗豐。
因為是第一次連線MySQL資料庫,很不熟練,也進行了一定的學習,但發現和sqlite3其實比較相似,只是在連線上,MySQL更強調c-s模式互動。
作業③
要求:熟練掌握 scrapy 中 Item、Pipeline 資料的序列化輸出方法;使用scrapy框架+Xpath+MySQL資料庫儲存技術路線爬取外匯網站資料。
候選網站:中國銀行網:https://www.boc.cn/sourcedb/whpj/
輸出資訊: Currency、TBP、CBP、TSP、CSP、Time
詳細程式碼見gitee🔗:作業3-實驗3.3
實踐過程
1.實現與selenium的對接
中國銀行網的貨幣資料也是動態渲染的,所以需要應用標題方法爬取資料
雖為核心方法,但方法同上,不多展示
2.網頁檢查
翻頁時url會變化:
for page in range(start, end + 1):
if page == 1:
url = f'https://www.boc.cn/sourcedb/whpj/index.html' # 根據實際網站結構生成 URL
yield scrapy.Request(url=url, callback=self.parse)
else:
url = f'https://www.boc.cn/sourcedb/whpj/index_{page - 1}.html' # 根據實際網站結構生成 URL
yield scrapy.Request(url=url, callback=self.parse)
由此可見在爬取的時候要跳過第一個tr(裝的是標題,但是屬性也為odd)
並且要注意每兩個tr為一組,一個沒有class屬性,一個class屬性值為odd
結合起來,爬取沒有屬性的tr就對了
def parse(self, response):
self.driver.get(response.url)
time.sleep(2) # 等待頁面載入
sel = Selector(text=self.driver.page_source)
currencies = sel.xpath('//table[@width="100%"]/tbody//tr[not(@class)]') # 選擇貨幣資料的容器
for index, currency in enumerate(currencies):
item = CurrencyItem()
try:
item['currency'] = currency.xpath('./td[1]/text()').get()
item['tbp'] = currency.xpath('./td[2]/text()').get()
item['cbp'] = currency.xpath('./td[3]/text()').get()
item['tsp'] = currency.xpath('./td[4]/text()').get()
item['csp'] = currency.xpath('./td[5]/text()').get()
item['time'] = currency.xpath('./td[8]/text()').get()
yield item
except IndexError as e:
self.logger.error(f"Error processing stock: {e} - Stock data may be incomplete.")
進行翻頁觀察,每五個小時更新一次,並且只需要爬取兩頁就能爬出一個時間上全部貨幣情況
所以我選取爬取前兩頁
3.piplines和settings配置
piplines.py:MySQL資料庫連線與存入應用
settings.py:不遵循爬蟲協議
都同第二題
實踐結果
實踐心得
透過後面兩道題我也直接接觸了scrapy和selenium對接的方法。在做的過程中除了方法掌握以外,更讓人糾結的應該就是資料爬取的xpath路徑編寫了,經常觀察錯了或者寫非法了,就會爬取失敗,這兩題也應用了很多除錯日誌,來觀察自己在編碼上的錯誤。
上課做得不好就只能課後總結了。做到這裡,基本理順了這次實踐的任務和方法,可能報告上還有不足,一些口語化表達不夠規範等等,也歡迎批評指正。