劉 哲,馬樂榮
(延安大學 數學與計算機科學學院,陜西 延安 716000)
隨著電子商務的快速發展,網上購物已經成為一種重要的購物形式,大規模電商平臺每天都會產生海量的商品交易數據。大量研究人員選擇電商平臺的商品數據作為實驗數據集[1-3]。針對這些數據的挖掘和分析,對于優化平臺建設、增加產品銷量、改進消費者購物體驗等,都有著重要的研究價值。
電商平臺出于性能和安全的考慮,往往通過異步加載的方法對數據進行展示。一些商品頁面在瀏覽器中查看時,可以顯示所有的商品數據,但通過爬蟲將頁面下載到本地,卻無法獲取想要采集的數據或者只能獲取到部分數據。例如,京東商城和天貓的商品詳情頁中,商品價格的顯示都是動態加載的,通過查看頁面源代碼的方式,會發現顯示價格的html標簽中沒有內容。針對這些大規模電商平臺數據,常見的做法是對頁面中的http請求進行抓包和分析,尋找數據源。這種做法通常是非常值得的,最終可以得到結構化的、完整的數據和更快的抓取速度。然而,大規模電商平臺頁面內容豐富、結構復雜,一個商品頁面能產生上百個請求。而且,接口所需參數往往難以獲取并且不好分析。如果對參數進行刪減,則得到的數據準確性難以保證,并且容易被平臺的反爬機制發現,導致被屏蔽。隨著平臺的業務發展和技術迭代,接口參數也會發生變動。
當需要獲取不同平臺的商品數據時,必須重新抓包、分析,時間成本高,技術難度大。本文通過Splash模擬瀏覽器操作,結合Scrapy爬蟲框架,設計并實現了一個商品信息采集系統,可以快速實現對不同平臺商品信息的采集。
大規模電商平臺擁有的商品數以萬計,商品種類繁多,為了方便消費者快速定位感興趣的產品,通常都提供了“商品檢索”的功能。國內一些常見的大規模電商平臺,例如京東商城、天貓、淘寶、當當網等,都提供了商品檢索的功能。這些平臺在商品詳情頁對商品進行描述時,都包括以下幾個部分:商品基本信息、規格參數、用戶評論,以及一組用于商品展示的圖片等。因此,本實驗主要針對商品詳情頁包含的四部分內容進行數據采集。
在電商平臺進行商品檢索時,首先需要在“商品搜索框”中輸入商品名稱,點擊“搜索按鈕”后會返回搜索結果的第一頁,選定此時的頁面地址作為程序的啟動URL(start_url)。同時,在電商平臺的商品檢索頁面下方,通常有一排用于顯示頁碼信息的分頁按鈕(例如京東,天貓,當當網等),如下圖1所示為京東商城商品檢索頁中的分頁按鈕,圖中箭頭所示文本輸入框中的頁碼,稱作頁號(page_num)(初始值設置為1,可根據需求適當調整)。

圖1京東平臺商品檢索頁面中的分頁按鈕
實驗設計的基本步驟如下(圖2為對應的程序流程圖):

圖2 系統流程圖
(1)首先,訪問start_url頁面。
(2)模擬瀏覽器操作,訪問商品檢索結果中的第page_num頁:
a.在圖1所示的文本輸入框中填入page_num。
b.點擊‘確定’按鈕,訪問檢索結果中的page_num頁。
c.根據實際情況,選擇性增加頁面滾動的操作(適用于那些通過頁面滾動的方式加載當前檢索頁剩余商品的電商平臺,例如京東商城、蘇寧易購等)。
(3)將檢索結果中的第page_num頁內容下載至本地,解析出頁面中的商品詳情頁地址。
(4)遍歷商品詳情頁,依次采集商品的基本信息、圖片、評論數據。
(5)將采集到的商品信息存入數據庫。
(6)page_num+=1,重復步驟(1)~(5),抓取剩余檢索頁中的商品信息。
(7)程序運行結束。
說明:通過這種方式,避免了對平臺的http請求進行抓包和分析的繁瑣工作,當需要采集其它平臺的商品數據,或者接口參數發生變動時,只需更換start_url,便可快速開始采集。
本次實驗使用Python 3.0作為編程語言,使用Splash模擬瀏覽器操作,并對商品頁面中的javascript代碼進行預渲染,爬蟲部分使用了Scrapy爬蟲框架對數據進行抓取和解析,數據的持久化儲存選擇了MongoDB[4,5],系統總體框架如圖3所示。
Splash是一種javascript渲染引擎,它的本質是一個帶有HTTPAPI的輕量級Web瀏覽器。其支持以下功能:并行處理多個Web頁面;為用戶返回經過渲染的頁面;可以方便的禁止圖片的加載,使頁面渲染速度得到極大的提升;在頁面上下文(pagecontext)中執行用戶自定義的javascript腳本等。由于Splash是以html的形式返回了網頁的document樹結構,我們可以方便的選擇自己熟悉的html解析器對頁面進行解析。通過執行用戶編寫的自定義腳本,Splash的作用類似于瀏覽器自動化工具。

圖3 系統框架
Scrapy是一款基于Python實現的,已經非常成熟的爬蟲框架。它提供了很多方便實用的功能,讓數據爬取變得簡單而高效。Scrapy自帶的選擇器(selector),讓用戶可以通過xpath表達式或者css選擇器從html/xml結構中選擇和提取數據,并對css選擇器進行了擴展。交互式的shell控制臺,在編寫或者調試爬蟲程序時非常有用。同時也提供了強大的可擴展性支持,允許用戶使用自定義的中間件對框架進行擴展。
MongoDB是當前很受歡迎的新一代數據庫,它由C++語言編寫,是一個基于分布式文件存儲的開源數據庫系統。相比于傳統關系數據庫,MongoDB對于大數據,高并發以及高可靠性有強大的支持。相比于其他的NoSQL數據庫,MongoDB的基于文檔的數據模型及其動態建模的特性使得它更加自由靈活,適用于各種應用場景。它支持幾乎所有的主流編程語言,例如Python,java,php等。
搭建實驗環境時,有幾項準備工作要做:①由于Splash是運行在Docker容器中的,所以需要先安裝Docker,安裝成功后,從DockerHub拉取Splash鏡像特別慢,容易拉取失敗,配置國內的鏡像源后可以解決,可以參考文獻[6]。②采用Scrapy+Splash結構時,還需要安裝Python包Scrapy-Splash,達到二者之間的無縫結合。安裝Scrapy-Splash時,要注意閱讀“配置”部分的內容。③通過“pipinstallscrapy”命令安裝爬蟲框架時,容易遇到因為超時拋出異常,無法下載成功的情況。可以選擇一些比較穩定、下載速度快的國內鏡像來下載,安裝其他Python包時也可使用該方法進行加速。表1中列出了一些常用的Python鏡像站。

表1 常用的國內鏡像站
關于Scrapy的框架結構,內部各組件間數據的處理和流向,官方文檔中有詳細的介紹,也可以參考文獻[7,8],本文不再贅述。這里主要介紹如何通過上述各種工具,快速實現對不同平臺商品信息的采集。下圖3給出了完整的系統框架,從圖中可以看出,商品信息的采集可以分為兩大類:一種是需要通過Splash對頁面中的內容進行動態渲染(模擬瀏覽器操作的本質也是動態執行自定義js腳本),例如,商品檢索頁面,商品詳情頁面。采集時,需要將默認的Scrapy Request對象,經過包Scrapy-Splash轉換為Splash可以接受的Splash Request對象,再由Splash訪問對應的頁面,返回經過渲染后的內容。另一種不需要經過Splash預渲染,可以直接通過訪問獲取到數據,例如,商品的展示圖片和商品評論。當Splash返回商品詳情頁的內容后,通過解析可以獲取到商品圖片的地址,Scrapy中有自帶的ImagesPipeline模塊,可以自動將圖片下載到本地文件系統中。不過為了便于持久化儲存,我們直接將下載到的圖片內容(二進制格式)記錄在對應的商品Item中,當商品評論下載完成時,再將Item儲存在數據庫中。由于我們在商品詳情頁瀏覽評論時,評論頁的跳轉產生的http請求較少,數據源容易確定,實驗中直接通過評論接口抓取商品的評論信息。
需要指出的是,在程序的運行過程中,檢索結果頁、商品詳情頁、商品圖片及商品評論的下載是同時進行的,Scrapy會自動維護請求和響應隊列,我們也可以為Request對象設置優先級(priority)來指定請求的執行順序。
本部分內容選取了實驗過程中遇到的一些難點問題,并給出了詳細的解決方案,總結如下。
問題1:程序運行一段時間后,頻繁出現Splash服務停止運行的情況。
Splash運行時使用的不是一塊固定的內存(use an unbound in-memory cache),隨著時間的推移,會消耗掉所有的內存資源。解決方法是,當使用過多內存時重新啟動該進程。可通過Splash中的—maxrss選項來指定該閾值。同時,為了防止異常錯誤導致的Splash服務停止運行,可以在Docker啟動命令中增加—restart選項。啟動一個需要長時間運行的Splash服務命令如下,當內存占用超過1000MB或者服務停止運行時,會重新啟動Splash服務:
“dockerrun-d-p8050:8050-restart=always scrapinghub/splash-maxrss1000”
問題2:當某個商品的評論數據采集完成后,需要存入數據庫,如何判斷這個時間點。
假設商品有n頁評論,通過循環的方式順序發出m1,m2,……,mn個異步請求。Scrapy框架中雖然可以通過request.meta屬性為每個請求標上序號,并傳遞給對應的response進行訪問,但由于無法控制服務器的響應時間和網絡傳輸的時間,這n個請求返回本地的順序是不確定的,通過判斷請求序號是否為n(最后一頁)來確定商品評論采集完成的方法是錯誤的。Scrapy本身也沒有給出這種情況下的解決方案。我們的解決方案是,為request.meta屬性(字典結構)設置一個鍵,對應的值初始化為空列表,每成功抓取一頁評論,向這個列表中增加一個計數元素(可以是數字0)。
該方法利用了Python語言中列表類型數據的特性,將用于計數的列表保存在本地,雖然請求是異步的,但是每個請求都指向了同一個列表(meta字段中實際保存的只是列表的內存地址)。當響應返回本地時,每個響應都可以通過判斷該列表的長度和評論接口返回的“maxPage”(評論頁總數)是否相等,來確定商品的所有評論信息是否采集完成。
商品詳情頁中圖片的采集也采用類似的方法。當圖片采集完成后,開始采集評論數據,評論數據采集完成后,存入數據庫。
問題3:setting.py中的自定義配置項問題。

表2 setting.py中的自定義配置項
Scrapy框架的setting模塊允許用戶自定義項目中的所有組件行為,為項目增添或者修改任何配置的工作,都是在setting.py文件中進行的。Scrapy提供了大量的內建配置項,表2中列出了一些可以優化項目運行的配置項。其中,DOWNLOAD_DELAY用于限制從同一網站連續下載頁面時,每次發送請求前程序應等待的時間,這樣做的目的是為了限制爬取速度,避免對服務器造成太大的沖擊,同時降低被爬蟲檢測程序發現的風險,該值的設置參考了文獻[9]。LOG_FILE用于長時間數據采集時,將運行日志保存為本地文件。RETRY_TIMES用于設置頁面下載失敗時,嘗試重新下載的最大次數,默認值是2次,可根據情況適當修改。RETRY_HTTP_CODES表示請求下載失敗時,根據http狀態碼,決定是否重新發起請求,這里在原來的配置項中增加狀態碼400,因為在大規模的數據采集過程中,會遇到少量的狀態碼為400的情況。
問題4:采集商品評論時,評論接口參數的值獲取困難。
實驗中還遇到一個難點,當采集京東商城商品評論時,評論接口參數callback的值無法確定,它的值都具有“fetchJSON_comment98vv5289”的形式,不同商品的評論接口中,該字符串末尾的幾位數字是隨機的。雖然去掉callback參數時也可以采集到評論數據,但采集一段時間后,接口返回“套接字錯誤”的提示,無法繼續獲取到評論數據。通過多次分析發現,在商品頁面中存在一個js變量comment Version,該變量的值和參數callback中最后幾位數字是一致的,通過編寫正則表達式和字符串拼接,就能得到完整的評論接口。
問題5:程序結構和邏輯的優化問題。
在對頁面內容進行解析,得到原始數據后,往往還需要進一步的加工處理,例如,去除字符串兩端的空白,檢查采集到的url字符串是否缺少“https:”協議頭等。隨著采集字段的增加,這樣的特殊處理越來越多,使主程序邏輯混亂,代碼結構臃腫。大多數項目在數據采集的過程中,對這個問題都沒有引起足夠的重視[10-12],當遇到大規模的數據采集時,往往擴展困難,容易造成維護噩夢。
解決方案就是將原始數據的處理過程從主程序中分離出去,通過使用Scrapy中的ItemLoader模塊可以實現這個功能。由于采集到的數據是以Item的形式進行傳遞的,ItemLoader為Item中的每個字段提供一個輸入處理器和一個輸出處理器,用戶可以在輸入、輸出處理器中,擴展或者覆蓋不同字段的解析規則。通過這樣的方式,實現了對程序中原始數據的解析和處理兩個過程的解耦,讓程序結構和邏輯更加清晰,系統更易維護。
本次實驗選取了“京東商城”和“當當網”2個平臺作為數據采集的對象,以“手機”作為檢索關鍵詞,分別對所設計的系統進行驗證。針對京東商城,采集前10頁的手機商品數據,在商品詳情頁中采集商品介紹、規格包裝、圖片和全部的評論數據。對于當當網,采集前5頁的手機商品數據,在商品詳情頁中,僅采集商品介紹和規格參數、圖片等基本信息。
在采集京東商城的商品數據時,我們將外層循環的迭代次數設置為1,程序運行時,只對一個檢索頁面中的商品信息進行采集。通過將page_num的值依次設置為1、2、3、…、10,分次采集了前10頁的商品信息。采集當當網時,外層循環的迭代次數設置為5,page_num初始值設置為1,程序運行時,直接采集前5頁商品基本信息。圖4給出對京東平臺進行分頁采集時,每頁數據的耗時曲線,檢索頁商品的評論總數曲線,及采集產生的請求數曲線。圖中的x軸代表采集時的檢索頁頁碼,兩個y軸代表商品評論總數(或者產生的請求個數)和總耗時。最后,在圖中標注出了對當當網一次性采集5頁商品基本信息時的總耗時。
仔細觀察圖中的耗時曲線,并且對比當當網不采集商品評論情況下的總耗時可以發現,數據采集的耗時主要受到商品評論數量的影響。圖中耗時曲線上有兩個異常點,第5頁和第10頁的耗時均高于前一個點,但評論數量卻均低于前一個點。通過分析日志發現,采集這兩頁數據時,程序中產生的retry_request更多,即由于各種原因導致某個請求下載失敗時,重新發起的采集請求。對比請求曲線中,這兩個點的值均高于前一個點,也可以印證這一分析。

圖4 商品信息采集結果展示
圖4客觀反映出了本文提出的數據采集方法的運行效率,在實踐中,用戶可以根據需要,采集商品的若干頁評論,提升采集效率。同時,由于實驗條件的限制,程序運行時的爬蟲系統,數據庫服務,支撐Splash及Docker服務的虛擬機等均運行在同一臺筆記本電腦上,一定程度上也影響了數據采集的效率,這也是以后的一個改進方向。數據采集的成功,直接說明了本文提出的大規模電商平臺商品信息采集方法的可行性,該方法可以實現對不同平臺商品數據的快速采集,為廣大的研究人員節省開發時間。
本文以大規模電商平臺商品交易數據為采集對象,提出了一種快速采集不同平臺商品數據的有效方法,并以京東商城、當當網為例進行數據采集。通過實驗證明,該方法能有效降低數據采集的難度,也可以用于單個平臺的數據采集。在后期的工作中,我們將采集不同平臺的商品數據,在此基礎上構建電商知識圖譜。