欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用scrapy實(shí)現(xiàn)增量式爬取方式

 更新時(shí)間:2022年06月21日 09:44:13   作者:馮子玉  
這篇文章主要介紹了使用scrapy實(shí)現(xiàn)增量式爬取方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

實(shí)現(xiàn)爬蟲(chóng)的增量式爬取有兩種方法,一是在獲得頁(yè)面解析的內(nèi)容后判斷該內(nèi)容是否已經(jīng)被爬取過(guò),二是在發(fā)送請(qǐng)求之前判斷要被請(qǐng)求的url是否已經(jīng)被爬取過(guò),前一種方法可以感知每個(gè)頁(yè)面的內(nèi)容是否發(fā)生變化,能獲取頁(yè)面新增或者變化的內(nèi)容,但是由于要對(duì)每個(gè)url發(fā)送請(qǐng)求,所以速度比較慢,而對(duì)網(wǎng)站服務(wù)器的壓力也比較大,后一種無(wú)法獲得頁(yè)面變化的內(nèi)容,但是因?yàn)椴挥脤?duì)已經(jīng)爬取過(guò)的url發(fā)送請(qǐng)求,所以對(duì)服務(wù)器壓力比較小,速度比較快,適用于爬取新增網(wǎng)頁(yè)

下面用一個(gè)小說(shuō)網(wǎng)站爬蟲(chóng)的例子來(lái)介紹在scrapy中這兩種方式的實(shí)現(xiàn)

1.要爬取的信息

在scrapy中,信息通過(guò)item來(lái)封裝,這里我定義兩個(gè)item,一個(gè)用于封裝每本小說(shuō)的信息,一個(gè)用于封裝每個(gè)章節(jié)的信息

1.BookItem

class BookItem(scrapy.Item):
    _id = scrapy.Field() #小說(shuō)id,用于定位章節(jié)信息,章節(jié)唯一
    novel_Name = scrapy.Field() #小說(shuō)名稱
    novel_Writer = scrapy.Field()#小說(shuō)作者
    novel_Type = scrapy.Field()#小說(shuō)類型
    novel_Status = scrapy.Field()#小說(shuō)狀態(tài),連載或者完結(jié)
    novel_UpdateTime = scrapy.Field()#最后更新時(shí)間
    novel_Words = scrapy.Field() #總字?jǐn)?shù)
    novel_ImageUrl = scrapy.Field()#封面圖片
    novel_AllClick = scrapy.Field()#總點(diǎn)擊
    novel_MonthClick = scrapy.Field()#月點(diǎn)擊
    novel_WeekClick = scrapy.Field()#周點(diǎn)擊
    novel_AllComm = scrapy.Field()#總推薦
    novel_MonthComm = scrapy.Field()#月推薦
    novel_WeekComm = scrapy.Field()#周推薦
    novel_Url = scrapy.Field()#小說(shuō)url
    novel_Introduction = scrapy.Field()#小說(shuō)簡(jiǎn)介

2.ChapterItem

 class ChapterItem(scrapy.Item):
    chapter_Url = scrapy.Field()#章節(jié)url
    _id = scrapy.Field()#章節(jié)id
    novel_Name = scrapy.Field()#小說(shuō)名稱
    chapter_Name = scrapy.Field()#章節(jié)名稱
    chapter_Content = scrapy.Field()#內(nèi)容
    novel_ID = scrapy.Field()#小說(shuō)id
    is_Error = scrapy.Field()#是否異常

2.解析信息

這里我是用的是scrapy自帶的通用爬蟲(chóng)模塊,只需要指定信息解析方式,需要跟進(jìn)的url就夠了

1.指定需要跟進(jìn)的url和回調(diào)函數(shù)

  allowed_domains = ["23us.so"] #允許爬取的域名
  start_urls = ["http://www.23us.so/xiaoshuo/414.html"]#種子url
  #跟進(jìn)的url
  rules=(
    Rule(LinkExtractor(allow=("xiaoshuo/\d*\.html")),callback="parse_book_message",follow=True),
    Rule(LinkExtractor(allow=("files/article/html/\d*?/\d*?.index.html")),callback="parse_book_chapter",follow=True),
    Rule(LinkExtractor(allow=("files/article/html/\d*?/\d*?/\d*?.html")),callback="parse_chapter_content",follow=True),
    Rule(LinkExtractor(allow=(".*")),follow=True),
  )

2.解析方法

1.解析書(shū)籍信息方法

#解析小說(shuō)信息頁(yè)面
  def parse_book_message(self,response):
    if not response.body:
      print(response.url+"已經(jīng)被爬取過(guò)了,跳過(guò)")
      return;
    ht = response.body.decode("utf-8")
    text = html.fromstring(ht)
    novel_Url = response.url
    novel_Name = text.xpath(".//dl[@id='content']/dd[1]/h1/text()")[0].split(" ")[0] if response.xpath(".//dl[@id='content']/dd[1]/h1/text()") else "None"
    novel_ImageUrl = text.xpath(".//a[@class='hst']/img/@src")[0] if response.xpath(".//a[@class='hst']/img/@src") else "None"
    novel_ID = int(response.url.split("/")[-1].split(".")[0]) if response.url.split("/")[-1].split(".") else "None"
    novel_Type = text.xpath(".//table[@id='at']/tr[1]/td[1]/a/text()") if response.xpath(".//table[@id='at']/tr[1]/td[1]/a/text()") else "None"
    novel_Writer = "".join(text.xpath(".//table[@id='at']/tr[1]/td[2]/text()")) if response.xpath(".//table[@id='at']/tr[1]/td[2]/text()") else "None"
    novel_Status = "".join(text.xpath(".//table[@id='at']/tr[1]/td[3]/text()")) if response.xpath(".//table[@id='at']/tr[1]/td[3]/text()") else "None"
    novel_Words = self.getNumber("".join(text.xpath(".//table[@id='at']/tr[2]/td[2]/text()"))) if response.xpath(".//table[@id='at']/tr[2]/td[2]/text()") else "None"
    novel_UpdateTime = "".join(text.xpath(".//table[@id='at']/tr[2]/td[3]/text()")) if response.xpath(".//table[@id='at']/tr[2]/td[3]/text()") else "None"
    novel_AllClick = int("".join(text.xpath(".//table[@id='at']/tr[3]/td[1]/text()"))) if response.xpath(".//table[@id='at']/tr[3]/td[1]/text()") else "None"
    novel_MonthClick = int("".join(text.xpath(".//table[@id='at']/tr[3]/td[2]/text()"))) if response.xpath(".//table[@id='at']/tr[3]/td[2]/text()") else "None"
    novel_WeekClick = int("".join(text.xpath(".//table[@id='at']/tr[3]/td[3]/text()"))) if response.xpath(".//table[@id='at']/tr[3]/td[3]/text()") else "None"
    novel_AllComm = int("".join(text.xpath(".//table[@id='at']/tr[4]/td[1]/text()"))) if response.xpath(".//table[@id='at']/tr[4]/td[1]/text()") else "None"
    novel_MonthComm = int("".join(text.xpath(".//table[@id='at']/tr[4]/td[3]/text()"))) if response.xpath(".//table[@id='at']/tr[4]/td[2]/text()") else "None"
    novel_WeekComm = int("".join(text.xpath(".//table[@id='at']/tr[4]/td[3]/text()"))) if response.xpath(".//table[@id='at']/tr[4]/td[3]/text()") else "None"
    pattern = re.compile('<p>(.*)<br')
    match = pattern.search(ht)
    novel_Introduction = "".join(match.group(1).replace("&nbsp;","")) if match else "None"
     #封裝小說(shuō)信息類
    bookitem = BookItem(
          novel_Type = novel_Type[0],
          novel_Name = novel_Name,
          novel_ImageUrl = novel_ImageUrl,
          _id = novel_ID,   #小說(shuō)id作為唯一標(biāo)識(shí)符
          novel_Writer = novel_Writer,
          novel_Status = novel_Status,
          novel_Words = novel_Words,
          novel_UpdateTime = novel_UpdateTime,
          novel_AllClick = novel_AllClick,
          novel_MonthClick = novel_MonthClick,
          novel_WeekClick = novel_WeekClick,
          novel_AllComm = novel_AllComm,
          novel_MonthComm = novel_MonthComm,
          novel_WeekComm = novel_WeekComm,
          novel_Url = novel_Url,
          novel_Introduction = novel_Introduction,
    )
    return bookitem

2.解析章節(jié)信息

def parse_chapter_content(self,response):
    if not response.body:
      print(response.url+"已經(jīng)被爬取過(guò)了,跳過(guò)")
      return;
    ht = response.body.decode('utf-8')
    text = html.fromstring(ht)
    soup = BeautifulSoup(ht)
    novel_ID = response.url.split("/")[-2]
    novel_Name = text.xpath(".//p[@class='fr']/following-sibling::a[3]/text()")[0]
    chapter_Name = text.xpath(".//h1[1]/text()")[0]
    '''
    chapter_Content = "".join("".join(text.xpath(".//dd[@id='contents']/text()")).split())
    if len(chapter_Content) < 25:
      chapter_Content = "".join("".join(text.xpath(".//dd[@id='contents']//*/text()")))
    pattern = re.compile('dd id="contents".*?>(.*?)</dd>')
    match = pattern.search(ht)
    chapter_Content = "".join(match.group(1).replace("&nbsp;","").split()) if match else "爬取錯(cuò)誤"
    '''
    result,number = re.subn("<.*?>","",str(soup.find("dd",id='contents')))
    chapter_Content = "".join(result.split())
    print(len(chapter_Content))
    novel_ID = response.url.split("/")[-2]
    return ChapterItem(
          chapter_Url = response.url,
          _id=int(response.url.split("/")[-1].split(".")[0]),
          novel_Name=novel_Name,
          chapter_Name=chapter_Name,
          chapter_Content= chapter_Content,
          novel_ID = novel_ID,
          is_Error = len(chapter_Content) < 3000
          )

3.scrapy中實(shí)現(xiàn)增量式爬取的幾種方式

1.緩存

通過(guò)開(kāi)啟緩存,將每個(gè)請(qǐng)求緩存至本地,下次爬取時(shí),scrapy會(huì)優(yōu)先從本地緩存中獲得response,這種模式下,再次請(qǐng)求已爬取的網(wǎng)頁(yè)不用從網(wǎng)絡(luò)中獲得響應(yīng),所以不受帶寬影響,對(duì)服務(wù)器也不會(huì)造成額外的壓力,但是無(wú)法獲取網(wǎng)頁(yè)變化的內(nèi)容,速度也沒(méi)有第二種方式快,而且緩存的文件會(huì)占用比較大的內(nèi)存,在setting.py的以下注釋用于設(shè)置緩存

#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

這種方式比較適合內(nèi)存比較大的主機(jī)使用,我的阿里云是最低配的,在爬取半個(gè)晚上接近27W個(gè)章節(jié)信息后,內(nèi)存就用完了

2.對(duì)item實(shí)現(xiàn)去重

本文開(kāi)頭的第一種方式,實(shí)現(xiàn)方法是在pipelines.py中進(jìn)行設(shè)置,即在持久化數(shù)據(jù)之前判斷數(shù)據(jù)是否已經(jīng)存在,這里我用的是mongodb持久化數(shù)據(jù),邏輯如下

  #處理書(shū)信息
  def process_BookItem(self,item):
    bookItemDick = dict(item)
    try:
      self.bookColl.insert(bookItemDick)
      print("插入小說(shuō)《%s》的所有信息"%item["novel_Name"])
    except Exception:
      print("小說(shuō)《%s》已經(jīng)存在"%item["novel_Name"])
  #處理每個(gè)章節(jié)
  def process_ChapterItem(self,item):
    try:
      self.contentColl.insert(dict(item))
      print('插入小說(shuō)《%s》的章節(jié)"%s"'%(item['novel_Name'],item['chapter_Name']))
    except Exception:
      print("%s存在了,跳過(guò)"%item["chapter_Name"])
  def process_item(self, item, spider):
    '''
    if isinstance(item,ChaptersItem):
      self.process_ChaptersItem(item)
    '''
    if isinstance(item,BookItem):
      self.process_BookItem(item)
    if isinstance(item,ChapterItem):
      self.process_ChapterItem(item)
    return item

兩種方法判斷mongodb中是否存在已有的數(shù)據(jù),一是先查詢后插入,二是先設(shè)置唯一索引或者主鍵再直接插入,由于mongodb的特點(diǎn)是插入塊,查詢慢,所以這里直接插入,需要將唯一信息設(shè)置為”_id”列,或者設(shè)置為唯一索引,在mongodb中設(shè)置方法如下

db.集合名.ensureIndex({"要設(shè)置索引的列名":1},{"unique":1})

需要用什么信息實(shí)現(xiàn)去重,就將什么信息設(shè)置為唯一索引即可(小說(shuō)章節(jié)信息由于數(shù)據(jù)量比較大,用于查詢的列最好設(shè)置索引,要不然會(huì)非常慢),這種方法對(duì)于服務(wù)器的壓力太大,而且速度比較慢,我用的是第二種方法,即對(duì)已爬取的url進(jìn)行去重

3.對(duì)url實(shí)現(xiàn)去重

對(duì)我而言,這種方法是最好的方法,因?yàn)樗俣瓤欤瑢?duì)網(wǎng)站服務(wù)器的壓力也比較小,不過(guò)網(wǎng)上的資料比較少,后來(lái)在文檔中發(fā)現(xiàn)scrapy可以自定義下載中間件,才解決了這個(gè)問(wèn)題

文檔原文如下

class scrapy.downloadermiddlewares.DownloaderMiddleware

process_request(request, spider) 當(dāng)每個(gè)request通過(guò)下載中間件時(shí),該方法被調(diào)用。

process_request() 必須返回其中之一: 返回 None 、返回一個(gè) Response 對(duì)象、返回一個(gè) Request對(duì)象或raise IgnoreRequest 。

如果其返回 None ,Scrapy將繼續(xù)處理該request,執(zhí)行其他的中間件的相應(yīng)方法,直到合適的下載器處理函數(shù)(downloadhandler)被調(diào)用, 該request被執(zhí)行(其response被下載)。

如果其返回 Response 對(duì)象,Scrapy將不會(huì)調(diào)用 任何 其他的 process_request() 或process_exception() 方法,或相應(yīng)地下載函數(shù); 其將返回該response。 已安裝的中間件的process_response() 方法則會(huì)在每個(gè)response返回時(shí)被調(diào)用。

如果其返回 Request 對(duì)象,Scrapy則停止調(diào)用process_request方法并重新調(diào)度返回的request。當(dāng)新返回的request被執(zhí)行后,相應(yīng)地中間件鏈將會(huì)根據(jù)下載的response被調(diào)用。

如果其raise一個(gè) IgnoreRequest 異常,則安裝的下載中間件的 process_exception()方法會(huì)被調(diào)用。如果沒(méi)有任何一個(gè)方法處理該異常,則request的errback(Request.errback)方法會(huì)被調(diào)用。如果沒(méi)有代碼處理拋出的異常,則該異常被忽略且不記錄(不同于其他異常那樣)。

所以只需要在process_request中實(shí)現(xiàn)去重的邏輯就可以了,代碼如下

class UrlFilter(object):
  #初始化過(guò)濾器(使用mongodb過(guò)濾)
  def __init__(self):
    self.settings = get_project_settings()
    self.client = pymongo.MongoClient(
      host = self.settings['MONGO_HOST'],
      port = self.settings['MONGO_PORT'])
    self.db = self.client[self.settings['MONGO_DB']]
    self.bookColl = self.db[self.settings['MONGO_BOOK_COLL']]
    #self.chapterColl = self.db[self.settings['MONGO_CHAPTER_COLL']]
    self.contentColl = self.db[self.settings['MONGO_CONTENT_COLL']]
  def process_request(self,request,spider):
    if (self.bookColl.count({"novel_Url":request.url}) > 0) or (self.contentColl.count({"chapter_Url":request.url}) > 0):
      return http.Response(url=request.url,body=None)

但是又會(huì)有一個(gè)問(wèn)題,就是有可能下次開(kāi)啟時(shí),種子url已經(jīng)被爬取過(guò)了,爬蟲(chóng)會(huì)直接關(guān)閉,后來(lái)想到一個(gè)笨方法解決了這個(gè)問(wèn)題,即在pipeline.py里的open_spider方法中再爬蟲(chóng)開(kāi)啟時(shí)刪除對(duì)種子url的緩存

def open_spider(self,spider):            
    self.bookColl.remove({"novel_Url":"http://www.23us.so/xiaoshuo/414.html"})

4.結(jié)果

目前一個(gè)晚上爬取了大約1000部小說(shuō)35W個(gè)章節(jié)的信息,還在繼續(xù)爬取中

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • python中內(nèi)置函數(shù)ord()返回字符串的ASCII數(shù)值實(shí)例詳解

    python中內(nèi)置函數(shù)ord()返回字符串的ASCII數(shù)值實(shí)例詳解

    ord()?函數(shù)是?chr()?函數(shù)(對(duì)于?8?位的?ASCII?字符串)的配對(duì)函數(shù),它以一個(gè)字符串(Unicode?字符)作為參數(shù),返回對(duì)應(yīng)的?ASCII?數(shù)值,或者?Unicode?數(shù)值,這篇文章主要介紹了python?中內(nèi)置函數(shù)ord()返回字符串的ASCII數(shù)值,需要的朋友可以參考下
    2022-07-07
  • Python+django實(shí)現(xiàn)文件下載

    Python+django實(shí)現(xiàn)文件下載

    本文是python+django系列的第二篇文章,主要是講述是先文件下載的方法和代碼,有需要的小伙伴可以參考下。
    2016-01-01
  • python類型強(qiáng)制轉(zhuǎn)換long to int的代碼

    python類型強(qiáng)制轉(zhuǎn)換long to int的代碼

    python的int型最大值和系統(tǒng)有關(guān),32位和64位系統(tǒng)結(jié)果是不同的,分別為2的31次方減1和2的63次方減1,可以通過(guò)sys.maxint查看此值
    2013-02-02
  • 數(shù)據(jù)清洗之如何用一行Python代碼去掉文本中的各種符號(hào)

    數(shù)據(jù)清洗之如何用一行Python代碼去掉文本中的各種符號(hào)

    我們?cè)谔幚砦谋镜臅r(shí)候往往需要對(duì)標(biāo)點(diǎn)符號(hào)進(jìn)行處理,下面這篇文章主要給大家介紹了關(guān)于數(shù)據(jù)清洗之如何用一行Python代碼去掉文本中的各種符號(hào)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11
  • 實(shí)例講解Python編程中@property裝飾器的用法

    實(shí)例講解Python編程中@property裝飾器的用法

    裝飾器中蘊(yùn)含著很多Python的進(jìn)階技巧,@property也不例外,比如文后會(huì)講到的快速進(jìn)行代碼重構(gòu)的一個(gè)例子,這里我們就來(lái)以實(shí)例講解Python編程中@property裝飾器的用法:
    2016-06-06
  • pytorch GAN偽造手寫(xiě)體mnist數(shù)據(jù)集方式

    pytorch GAN偽造手寫(xiě)體mnist數(shù)據(jù)集方式

    今天小編就為大家分享一篇pytorch GAN偽造手寫(xiě)體mnist數(shù)據(jù)集方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-01-01
  • 關(guān)于python的bottle框架跨域請(qǐng)求報(bào)錯(cuò)問(wèn)題的處理方法

    關(guān)于python的bottle框架跨域請(qǐng)求報(bào)錯(cuò)問(wèn)題的處理方法

    這篇文章主要介紹了關(guān)于python的bottle框架跨域請(qǐng)求報(bào)錯(cuò)問(wèn)題的處理方法,需要的朋友可以參考下
    2017-03-03
  • Python 中的 else詳解

    Python 中的 else詳解

    這篇文章主要介紹了Python 中的 else詳解的相關(guān)資料,需要的朋友可以參考下
    2016-04-04
  • python垃圾回收機(jī)制(GC)原理解析

    python垃圾回收機(jī)制(GC)原理解析

    這篇文章主要介紹了python垃圾回收機(jī)制(GC)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Python實(shí)現(xiàn)base64編碼

    Python實(shí)現(xiàn)base64編碼

    這篇文章介紹了Python實(shí)現(xiàn)base64編碼的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06

最新評(píng)論