有了前兩篇的基礎,接下來通過抓取淘寶和天貓的數據來詳細說明,如何通過Scrapy爬取想要的內容。完整的代碼:[不帶數據庫版本][ 數據庫版本]。
需求
通過淘寶的搜索,獲取搜索出來的每件商品的銷量、收藏數、價格。
解決思路
- 首先,打開淘寶的搜索頁面,在里面輸入:硬盤,選中列表模式(因為列表模式沒有廣告)。
- 獲取到現在瀏覽器上面的地址:
https://s.taobao.com/search?q=硬盤&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.50862.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170316&style=list
- 在出現的商品列表中有很多硬盤,我們需要獲取到這些商品的詳細信息,也就是它的跳轉鏈接,比如:
//detail.tmall.com/item.htm?spm=a230r.1.14.19.QzLRla&id=40000831870&ad_id=&am_id=&cm_id=140105335569ed55e27b&pm_id=&abbucket=14
- 然后再把詳細地址的內容全部請求出來,里面包含了銷量、價格、收藏數量。
所以,最終的目的是通過獲取兩個頁面的內容,一個是搜索結果,從里面找出來每一個商品的詳細地址,然后第二個是商品詳細內容,從里面獲取到銷量、價格等。
下載網頁
有了思路現在我們先下載搜索結果頁面,然后再下載頁面中每一項詳細信息頁面。
def _parse_handler(self, response):
''' 下載頁面 """
self.driver.get(response.url)
pass
很簡單,通過self.driver.get(response.url)
就能使用selenium
下載內容,如果直接使用response中的網頁內容是靜態的。
獲取想要的內容(Selector)
上面說了如何下載內容,當我們下載好內容后,需要從里面去獲取我們想要的有用信息,這里就要用到選擇器,選擇器構造方式比較多,只介紹一種,這里看詳細信息:
>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').extract()
[u'good']
這樣就通過xpath取出來了good
這個單詞,更詳細的xpath教程點擊這里。
Selector 提供了很多方式出了xpath,還有css選擇器,正則表達式,中文教程看這個,具體內容就不多說,只需要知道這樣可以快速獲取我們需要的內容。
處理內容
簡單的介紹了怎么獲取內容后,現在我們從第一個搜索結果中獲取我們想要的商品詳細鏈接,通過查看網頁源代碼可以看到,商品的鏈接在這里:
...
<p class="title">
<a class="J_ClickStat" data-nid="523242229702" target="_blank" trace="msrp_auction" traceidx="5" trace-pid="" data-spm-anchor-id="a230r.1.14.46">WD/西部數據 WD30EZRZ臺式機3T電腦<span class="H">硬盤</span> 西數藍盤3TB 替綠盤</a>
</p>
...
使用之前的規則來獲取到a元素的href屬性就是需要的內容:
selector = Selector(text=self.driver.page_source) # 這里不要省略text因為省略后Selector使用的是另外一個構造函數,self.driver.page_source是這個網頁的html內容
selector.css(".title").css(".J_ClickStat").xpath("./@href").extract()
簡單說一下,這里通過css工具取了class叫title
的p元素,然后又獲取了class是J_ClickStat
的a元素,最后通過xpath規則獲取a元素的href中的內容。啰嗦一句css中如果是取id則應該是selector.css("#title")
,這個和css中的選擇器是一致的。
同理,我們獲取到商品詳情后,以獲取銷量為例,查看源代碼:
<ul class="tm-ind-panel">
<li class="tm-ind-item tm-ind-sellCount" data-label="月銷量"><div class="tm-indcon"><span class="tm-label">月銷量</span><span class="tm-count">881</span></div></li>
<li class="tm-ind-item tm-ind-reviewCount canClick tm-line3" id="J_ItemRates"><div class="tm-indcon"><span class="tm-label">累計評價</span><span class="tm-count">4593</span></div></li>
<li class="tm-ind-item tm-ind-emPointCount" data-spm="1000988"><div class="tm-indcon"><a target="_blank"><span class="tm-label">送天貓積分</span><span class="tm-count">55</span></a></div></li>
</ul>
獲取月銷量:
selector.css(".tm-ind-sellCount").xpath("./div/span[@class='tm-count']/text()").extract_first()
獲取累計評價:
selector.css(".tm-ind-reviewCount").xpath("./div[@class='tm-indcon']/span[@class='tm-count']/text()").extract_first()
最后把獲取出來的數據包裝成Item返回。淘寶或者天貓他們的頁面內容不一樣,所以規則也不同,需要分開去獲取想要的內容。
Item使用
Item是scrapy中獲取出來的結果,后面可以處理這些結果。
定義
Item一般是放到items.py
中
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
創建
>>> product = Product(name='Desktop PC', price=1000)
>>> print product
Product(name='Desktop PC', price=1000)
使用值
>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC
>>> product['price']
1000
>>> product['last_updated']
Traceback (most recent call last):
...
KeyError: 'last_updated'
>>> product.get('last_updated', 'not set')
not set
>>> product['lala'] # getting unknown field
Traceback (most recent call last):
...
KeyError: 'lala'
>>> product.get('lala', 'unknown field')
'unknown field'
>>> 'name' in product # is name field populated?
True
>>> 'last_updated' in product # is last_updated populated?
False
>>> 'last_updated' in product.fields # is last_updated a declared field?
True
>>> 'lala' in product.fields # is lala a declared field?
False
設置值
>>> product['last_updated'] = 'today'
>>> product['last_updated']
today
>>> product['lala'] = 'test' # setting unknown field
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'
這里只需要注意一個地方,不能通過product.name
的方式獲取,也不能通過product.name = "name"
的方式設置值。
添加Pipeline過濾結果
當Item在Spider中被收集之后,它將會被傳遞到Item Pipeline,一些組件會按照一定的順序執行對Item的處理。
每個item pipeline組件(有時稱之為“Item Pipeline”)是實現了簡單方法的Python類。他們接收到Item并通過它執行一些行為,同時也決定此Item是否繼續通過pipeline,或是被丟棄而不再進行處理。
以下是item pipeline的一些典型應用:
- 清理HTML數據
- 驗證爬取的數據(檢查item包含某些字段)
- 查重(并丟棄)
- 將爬取結果保存到數據庫中
現在實現一個Item過濾器,我們把獲取出來如果是None的數據賦值為0,如果Item對象是None則扔掉這條數據。
pipeline一般是放到pipelines.py
中
def process_item(self, item, spider):
if item is not None:
if item["p_standard_price"] is None:
item["p_standard_price"] = item["p_shop_price"]
if item["p_shop_price"] is None:
item["p_shop_price"] = item["p_standard_price"]
item["p_collect_count"] = text_utils.to_int(item["p_collect_count"])
item["p_comment_count"] = text_utils.to_int(item["p_comment_count"])
item["p_month_sale_count"] = text_utils.to_int(item["p_month_sale_count"])
item["p_sale_count"] = text_utils.to_int(item["p_sale_count"])
item["p_standard_price"] = text_utils.to_string(item["p_standard_price"], "0")
item["p_shop_price"] = text_utils.to_string(item["p_shop_price"], "0")
item["p_pay_count"] = item["p_pay_count"] if item["p_pay_count"] is not "-" else "0"
return item
else:
raise DropItem("Item is None %s" % item)
最后需要在settings.py
中添加這個pipeline
ITEM_PIPELINES = {
'TaoBao.pipelines.TTDataHandlerPipeline': 250,
'TaoBao.pipelines.MysqlPipeline': 300,
}
后面那個數字越小,則執行的順序越靠前,這里先過濾處理數據,獲取到正確的數據后,再執行TaoBao.pipelines.MysqlPipeline
添加數據到數據庫。
可能會遇到的一些問題
IDE調試
之前說的方式都是直接通過命令scrapy crawl tts
來啟動。怎么用IDE的調試功能呢?很簡單通過main函數啟動爬蟲:
# 寫到Spider里面
if __name__ == "__main__":
settings = get_project_settings()
process = CrawlerProcess(settings)
spider = TmallAndTaoBaoSpider
process.crawl(spider)
process.start()
302重定向的問題
在獲取數據的時候,很多時候會遇到網頁重定向的問題,scrapy會返回302然后不會自動重定向后繼續爬取新地址,在scrapy的設置中,可以通過配置來開啟重定向,這樣即使域名是重定向的scrapy也會自動到最終的地址獲取內容。
解決方案:settings.py
中添加REDIRECT_ENABLED = True
命令行參數傳遞
很多時候爬蟲都有自定義數據,比如之前寫的是硬盤關鍵字,現在通過參數的方式怎么傳遞呢?
解決方案:
- 重寫初始化函數
def __init__(self, *args, **kwargs):
直接在函數參數添加自定義參數:
dt 和 keys是自定義的參數。def __init__(self, dt=None, keys=None, *args, **kwargs): super(TmallAndTaoBaoSpider, self).__init__(*args, **kwargs)
- 命令行使用。命令行是通過
-a
參數來傳遞的,需要注意的是-a
只能傳遞一個參數,如果需要傳遞多個參數,使用多次-a
scrapy crawl tts -a keys="硬盤,光驅" -a dt="20170316"
- IDE中main函數使用。
if __name__ == "__main__": settings = get_project_settings() process = CrawlerProcess(settings) spider = TmallAndTaoBaoSpider process.crawl(spider, keys="硬盤,光驅", dt="20170316") process.start()
數據不全(selenium并不知道什么時候ajax請求完成),延時處理
大部分時候,我們可以取到完整的網頁信息,如果網頁的ajax請求太多,網速太慢的時候,selenium并不知道什么時候ajax請求完成,這個時候如果通過self.driver.get(response.url)
獲取頁面,然后通過Selector取數據,很可能還沒加載完成取不到數據。
解決方案:通過selenium提供的工具來延遲獲取內容,直到獲取到數據,或者超時。
def _wait_get(self, method):
"""
延時獲取,如果10秒鐘還沒有獲取完成,則返回失敗
:param method:
:return:
"""
result = None
try:
result = WebDriverWait(self.driver, 10).until(method)
except:
self.__error("超時獲取:%s %s" % (self.driver.current_url, self.driver.title))
log.e()
return result
這里以獲取評論為例:
item['p_comment_count'] = self._wait_get(lambda dr: Selector(text=self.driver.page_source).xpath("http://li/div/div[@class='tb-rate-counter']/a/strong/text()").extract_first())
在10秒以內會一直執行這個lambada函數:
lambda dr: Selector(text=self.driver.page_source).xpath("http://li/div/div[@class='tb-rate-counter']/a/strong/text()").extract_first()
直到這個函數返回的不是None,或者10秒后返回超時。
robots.txt不讓爬取
Scrapy爬取遵循robots協議,就是網站定義了哪些數據可以爬取,哪些不能爬取,如果網站不允許爬取,還是想爬怎么辦?
解決方案:
在settings.py
中忽略robots協議,添加參數:ROBOTSTXT_OBEY = False
請求數量配置
默認的數量是16,可以修改大一些,settings.py
中設置:CONCURRENT_REQUESTS = 50
** 免責聲明:該內容只為傳遞知識,如果用做他途后果自負。**
不登高山,不知天之高也;不臨深溪,不知地之厚也
感謝指點、交流、喜歡