爬蟲對象選擇
打開中國天氣網站,例如我要爬取廈門近 40 天的天氣情況,打開對應的網頁。“weather40d”目錄是近 40 天的天氣,“101230201”是廈門城市的 id。
http://www.weather.com.cn/weather40d/101230201.shtml
打開開發者工具,觀察到每一天的天氣數據還是比較復雜的,所以我們還是要找 API。
在 network 中觀察,Fetch/XHG 中沒有任何請求的交互,說明這個網頁要么是靜態的,要么是用 JavaScript 動態生成的。
隨便找一個在頁面中的字符串,例如以 11 月 7 號的天氣“多雲轉小雨”為例,可以看到搜索出一個結果。
查看這個 JavaScript 文件,可以看到這里羅列了一系列的數據。
打開詳細的數據,發現這個字典里面的數據可以和網頁的數據對應,說明這個就是我們想要的 js 文件。
查看這個請求的請求頭,得到這個 js 文件的 url。
但是訪問這個 url,發現回顯 403 錯誤。403 錯誤表示資源不可用,也就是服務器理解客戶的請求,但拒絕處理它,通常由於服務器上文件或目錄的權限設置導致的。
也就是說只有特定的地方發起請求才能獲取這個資源,查看請求頭,發現 “Referer” 字段的值為中國天氣網站。Referer 字段用於告訴服務器該網頁是從哪個頁面鏈接過來的,因此很有可能我們想要的 js 文件只接受來源於該網站的請求。同時還有一個“User-Agent”字段也需要設置,User-Agent 字段會告訴網站服務器訪問者是通過什么工具來請求的。
創建 scrapy 工程
Scrapy 是適用於 Python 的一個快速、高層次的屏幕抓取和 web 抓取框架,用於抓取 web 站點並從頁面中提取結構化的數據,提供了多種類型爬蟲的基類。
和之前用 requests 庫不同,要使用 Scrapy 框架需要創建一個 Scrapy 工程,可以在 cmd 使用命令創建。
scrapy startproject 工程名
Scrapy 的項目的目錄結構如下:
scrapy.cfg
myproject/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
spider1.py
spider2.py
...
接着可以進入目錄下進行初始化,使用命令指示要爬取的 Url,Scrapy 會為之建立一個爬蟲文件。
cd 工程名
scrapy genspider 爬蟲名 Url
例如我新建一個工程 weather,並且初始化一個爬蟲文件 XMWeather 並指定 url 如下。
scrapy startproject weather
cd weather
scrapy genspider XMWeather http://d1.weather.com.cn/calendar_new/2021/101230201_202111.html
代碼編寫
items.py
Scrapy 提供 Item 類,實例化后的 Item 對象是種簡單的容器,用於保存了爬取到得數據。其提供了類似於字典的 API 以及用於聲明可用字段的簡單語法,Item 使用簡單的 class 定義語法以及 Field 對象來聲明。
注意由於我們爬取的是包含 JSON 字符串的 JavaScript 文件,所以只需要將 JSON 字符串存起來即可,所以 Item 里面只有一個 content 字段。
import scrapy
class WeatherItem(scrapy.Item):
content = scrapy.Field()
spiders/XMWeather.py
Spider 類定義了如何爬取網站,包括了爬取的動作以及如何從網頁的內容中提取結構化數據。當我們編寫 Spider 類時需要在 spiders 文件夾下創建 py 文件,由於我們剛剛有初始化所以這里已經有一個 py 文件了。
Scrapy 爬取之后將返回一個 response 對象,parse() 是處理下載的 response 的默認方法,這個方法應該將數據處理后填充進一個實例化后的 Item 類對象中並返回。使用 response.body_as_unicode() 可以獲得 response 對象的內容,由於我定義的 Item 只有一個字段,所以設置這個字段並返回即可。
import scrapy
from weather.items import WeatherItem
class XmweatherSpider(scrapy.Spider):
name = 'XMWeather'
allowed_domains = ['d1.weather.com.cn']
start_urls = ['http://d1.weather.com.cn/calendar_new/2021/101230201_202111.html']
def parse(self, response):
#實例化 Item 對象
item = WeatherItem()
item['content'] = response.body_as_unicode()[11:]
return item
觀察 Response 的內容,發現除了開頭的 “var fc40 = [”這一串字符串,后面的內容都是以 json 的格式存在的。所以只需要把一開始的 “var fc40 = [”刪掉,請求的內容就可以以 JSON 的格式解析。
pipelines.py
當 Item 在 Spider中被收集之后,它將會被傳遞到 Item Pipeline,pipelines.py 中的組件將會按照一定的順序執行對 Item 的處理。每個 item pipeline 組件是實現了簡單方法的 Python 類,接收到 Item 后將執行一些行為進行處理,例如存儲入文件當中。
此處我編寫了 WeatherPipeline 類用於將 item 中的 content 字段解析為 JSON 對象,然后存儲如 xls 文件中。
from itemadapter import ItemAdapter
import json
import xlwt
class WeatherPipeline:
def process_item(self, item, spider):
weathers = json.loads(item['content'])
writebook = xlwt.Workbook()
sheet = writebook.add_sheet('Sheet1')
keys = ['date','nlyf','nl','w1','wd1','max','min','jq','t1','hmax','hmin','hgl','alins','als']
for i in range(len(keys)):
sheet.write(0, i, keys[i])
for i in range(len(weathers)):
for j in range(len(keys)):
sheet.write(i + 1, j, weathers[i][keys[j]])
writebook.save('weathers.xls')
settings.py
Scrapy 的 settings 提供了定制 Scrapy 組件的方法,可以控制包括核心、插件、pipeline 及 spider 組件。設定為代碼提供了提取以 key-value 映射的配置值的的全局命名空間,settings 同時也是選擇當前激活的 Scrapy 項目的方法。
由於 url 有來源檢測,所以我們要設置 DEFAULT_REQUEST_HEADERS 修改報文的請求頭。ITEM_PIPELINES 則是指定了用來處理數據的 Pipeline,后面的值表示的是優先級。
BOT_NAME = 'weather'
SPIDER_MODULES = ['weather.spiders']
NEWSPIDER_MODULE = 'weather.spiders'
ROBOTSTXT_OBEY = True
DEFAULT_REQUEST_HEADERS = {
"Referer":"http://www.weather.com.cn/",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
}
ITEM_PIPELINES = {
'weather.pipelines.WeatherPipeline': 300
}
項目運行
在項目目錄下使用 scrapy crawl 命令可以運行該項目,也可以加上 nolog 使其不輸出日志信息。
scrapy crawl 爬蟲名
scrapy crawl 爬蟲名 --nolog
運行項目之后的效果如下:
scrapy crawl XMWeather --nolog