Scrapy抓取噹噹網82萬冊圖書數據

今天主要分享一下使用Scrapy框架抓取噹噹網的圖書數據。

前言:

scrapy框架自帶twisted線程池,默認10個線程,在爬蟲這種IO密集型任務中可充分利用請求返回的等待時間。本次爬蟲單機運行,2小時抓取完82萬條圖書信息。

Github地址:點這裡

噹噹圖書數據 密碼:dt1q

文章目錄結構:

在Windows命令行下進入當前文件目錄輸入:tree /f > tree.txt 命令,出現當前文件的目錄

C:.n│ run.pyn│ scrapy.cfgn│ n└─dangdangn │ items.pyn │ pipelines.pyn │ settings.pyn │ __init__.pyn │ n └─spidersn dangdang.pyn __init__.pyn

想必大家對Scrapy框架中各模塊的用途已經比較了解了,我這裡先做下簡要介紹,下面進行詳細展開。run.py:設置在pycharm控制台運行程序,items.py:定義抓取的數據欄位包括哪些, pipelines.py :將抓取到的數據持久化到本地, settings.py:設置運行參數,如資料庫配置、時間間隔、消息隊列 , dangdang.py:抓取數據的核心程序,包括解析頁面,匹配欄位,異常處理。下面我結合程序,進行詳細說明。

運行管理:

run.py

from scrapy import cmdlinencmdline.execute("scrapy crawl dangdangspider".split())n

一般我們通過cmd進入當前文件路徑的方式運行程序,為更方便我們調試程序,這裡我們可以通過引入cmdline的方式,直接運行此文件可在pycharm控制台下查看程序運行效果。

settings.py

# -*- coding: utf-8 -*-nn# Scrapy settings for dangdang projectnnBOT_NAME = dangdangnnSPIDER_MODULES = [dangdang.spiders]nNEWSPIDER_MODULE = dangdang.spidersnn# Obey robots.txt rulesnROBOTSTXT_OBEY = FalsennDOWNLOAD_DELAY = 1nnSCHEDULER = "scrapy_redis.scheduler.Scheduler" #調度nDUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #去重nSCHEDULER_PERSIST = True #不清理Redis隊列nSCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue" #隊列nnITEM_PIPELINES = {n dangdang.pipelines.DangdangPipeline: 300,n}nnMONGODB_HOST = 127.0.0.1nMONGODB_PORT = 27017nMONGODB_DBNAME = "dangdang"nMONGODB_DOCNAME = "saveinto_2"n

settings中需要設置一些程序中運行的關鍵配置信息:

DOWNLOAD_DELAY:設置爬取的延時等待時間,這裡設置1s

SCHEDULER:scrapy中的調度器配置

DUPEFILTER_CLASS:對爬取的鏈接去重,當發現有重複鏈接時對第二個鏈接不再爬取

SCHEDULER_PERSIST:如果這一項設為True,那麼在Redis中的URL隊列不會被清理掉,但是在分散式爬蟲共享URL時,要防止重複爬取。如果設為False,那麼每一次讀取URL後都會將其刪掉,但弊端是爬蟲暫停後重新啟動,他會重新開始爬取。

SCHEDULER_QUEUE_CLASS:爬蟲的請求調度演算法,這裡有三種可供選擇

  1. scrapy_redis.queue.SpiderQueue:隊列。先入先出隊列,先放入Redis的請求優先爬取;

  2. scrapy_redis.queue.SpiderStack:棧。後放入Redis的請求會優先爬取;

  3. scrapy_redis.queue.SpiderPriorityQueue:優先順序隊列。根據優先順序演算法計算哪個先爬哪個後爬,比較麻煩

下面的一些是對mongodb的配置,默認主機127.0.0.1,埠27017,資料庫名稱設為dangdang,表名稱設為saveinto_2。

Redis的信息不配置的話,默認在本地運行

下載和存儲管理:

items.py

# -*- coding: utf-8 -*-nnimport scrapynnclass DangdangItem(scrapy.Item):n _id = scrapy.Field()n title = scrapy.Field()n comments = scrapy.Field()n time = scrapy.Field()n press = scrapy.Field() #出版社n price = scrapy.Field()n discount = scrapy.Field()n category1 = scrapy.Field() # 種類(小)n category2 = scrapy.Field() # 種類(大)n

我們需要抓取的欄位包括:書名、評論數量、出版時間、出版社、價格、折扣、圖書歸屬的大種類、圖書歸屬的小種類(大種類如:童書,小種類如:中國兒童文學 / 科普 / 百科 / 3~6歲)

pipelines.py

# -*- coding: utf-8 -*-nimport pymongonfrom scrapy.conf import settingsnfrom .items import DangdangItemnnclass DangdangPipeline(object):n def __init__(self):n host = settings[MONGODB_HOST]n port = settings[MONGODB_PORT]n db_name = settings[MONGODB_DBNAME]n client = pymongo.MongoClient(host=host,port=port)n tdb = client[db_name]n self.post = tdb[settings[MONGODB_DOCNAME]]nn def process_item(self, item, spider):n 先判斷itme類型,在放入相應資料庫n if isinstance(item,DangdangItem):n try:n book_info = dict(item) #n if self.post.insert(book_info):n except Exception:n passn return itemn

這裡我們在__init__函數中首先初始化連接到mongodb資料庫,關於mongodb的配置信息見上面的setting.py介紹。接下來在process_item函數中通過 isinstance 判斷傳入的item類型,然後持久化到對應的資料庫。

spider抓取程序:

終於寫到抓取的關鍵環節了。在貼上代碼之前,先對抓取的頁面和鏈接做一個分析:

http://category.dangdang.com/pg4-cp01.25.17.00.00.00.htmln

這個是噹噹網圖書的鏈接,經過分析發現:大種類的id號對應 cp01.25 中的25,小種類對應id號中的第三個 17,pg4代表大種類 —>小種類下圖書的第17頁信息。

為了在抓取圖書信息的同時找到這本圖書屬於哪一大種類下的小種類的歸類信息,我們需要分三步走,第一步:大種類劃分,在首頁找到圖書各大種類名稱和對應的id號;第二步,根據大種類id號生成的鏈接,找到每個大種類下的二級子種類名稱,及對應的id號;第三步,在大種類 —>小種類的歸類下抓取每本圖書信息。

分步驟介紹下:

1、我們繼承RedisSpider作為父類,start_urls作為初始鏈接,用於請求首頁圖書數據

# -*- coding: utf-8 -*-nimport scrapynimport requestsnfrom scrapy import Selectornfrom lxml import etreenfrom ..items import DangdangItemnfrom scrapy_redis.spiders import RedisSpidernnclass DangdangSpider(RedisSpider):n name = dangdangspidern redis_key = dangdangspider:urlsn allowed_domains = ["dangdang.com"]n start_urls = http://category.dangdang.com/cp01.00.00.00.00.00.htmlnn def start_requests(self):n user_agent = Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 n Safari/537.36 SE 2.X MetaSr 1.0n headers = {User-Agent: user_agent}n yield scrapy.Request(url=self.start_urls, headers=headers, method=GET, callback=self.parse)n

2、在首頁中抓取大種類的名稱和id號,其中yield回調函數中傳入的meta值為本次匹配出的大種類的名稱和id號

def parse(self, response):n user_agent = Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 n Safari/537.36 SE 2.X MetaSr 1.0n headers = {User-Agent: user_agent}n lists = response.body.decode(gbk)n selector = etree.HTML(lists)n goodslist = selector.xpath(//*[@id="leftCate"]/ul/li)n for goods in goodslist:n try:n category_big = goods.xpath(a/text()).pop().replace( ,) # 大種類n category_big_id = goods.xpath(a/@href).pop().split(.)[1] # idn category_big_url = "http://category.dangdang.com/pg1-cp01.{}.00.00.00.00.html".n format(str(category_big_id))n # print("{}:{}".format(category_big_url,category_big))n yield scrapy.Request(url=category_big_url, headers=headers,callback=self.detail_parse,n meta={"ID1":category_big_id,"ID2":category_big})n except Exception:n passn

3、根據傳入的大種類的id號抓取每個大種類下的小種類圖書標籤,yield回調函數中傳入的meta值為大種類id號和小種類id號

def detail_parse(self, response):n n ID1:大種類ID ID2:大種類名稱 ID3:小種類ID ID4:小種類名稱n n url = http://category.dangdang.com/pg1-cp01.{}.00.00.00.00.html.format(response.meta["ID1"])n category_small = requests.get(url)n contents = etree.HTML(category_small.content.decode(gbk))n goodslist = contents.xpath(//*[@class="sort_box"]/ul/li[1]/div/span)n for goods in goodslist:n try:n category_small_name = goods.xpath(a/text()).pop().replace(" ","").split(()[0]n category_small_id = goods.xpath(a/@href).pop().split(.)[2]n category_small_url = "http://category.dangdang.com/pg1-cp01.{}.{}.00.00.00.html".n format(str(response.meta["ID1"]),str(category_small_id))n yield scrapy.Request(url=category_small_url, callback=self.third_parse, meta={"ID1":response.meta["ID1"],n "ID2":response.meta["ID2"],"ID3":category_small_id,"ID4":category_small_name})nn # print("============================ {}".format(response.meta["ID2"])) # 大種類名稱n # print(goods.xpath(a/text()).pop().replace(" ","").split(()[0]) # 小種類名稱n # print(goods.xpath(a/@href).pop().split(.)[2]) # 小種類IDn except Exception:n passn

4、抓取各大種類——>小種類下的圖書信息

def third_parse(self,response):n for i in range(1,101):n url = http://category.dangdang.com/pg{}-cp01.{}.{}.00.00.00.html.format(str(i),response.meta["ID1"],n response.meta["ID3"])n try:n contents = requests.get(url)n contents = etree.HTML(contents.content.decode(gbk))n goodslist = contents.xpath(//*[@class="list_aa listimg"]/li)n for goods in goodslist:n item = DangdangItem()n try:n item[comments] = goods.xpath(div/p[2]/a/text()).pop()n item[title] = goods.xpath(div/p[1]/a/text()).pop()n item[time] = goods.xpath(div/div/p[2]/text()).pop().replace("/", "")n item[price] = goods.xpath(div/p[6]/span[1]/text()).pop()n item[discount] = goods.xpath(div/p[6]/span[3]/text()).pop()n item[category1] = response.meta["ID4"] # 種類(小)n item[category2] = response.meta["ID2"] # 種類(大)n except Exception:n passn yield itemn except Exception:n passn

抓取的效果:

5、從mongodb導出數據:

>mongoexport -d dangdang -c saveinto_2 -f discount,title,comments,price,time,category1,category2 --type=csv -o dangdang.csvn

結束,謝謝大家耐心地看到這裡

推薦閱讀:

vscode怎麼編譯python?
python 獲得列表中每個元素出現次數的最快方法?

TAG:爬虫计算机网络 | Python |