網絡爬蟲之scrapy框架詳解


twisted介紹

Twisted是用Python實現的基於事件驅動的網絡引擎框架,scrapy正是依賴於twisted,

它是基於事件循環的異步非阻塞網絡框架,可以實現爬蟲的並發。

twisted是什么以及和requests的區別:

  1. request是一個python實現的可以偽造瀏覽器發送Http請求的模塊,它封裝了socket發送請求
  2. twisted是基於時間循環的異步非阻塞的網絡框架,它也封裝了socket發送請求,但是他可以單線程的完成並發請求。

twisted的特點是:

  • 非阻塞:不等待
  • 異步:回調
  • 事件循環:一直循環去檢查狀態

scrapy的pipeline文件和items文件

這兩個文件有什么作用

先看看我們上篇的示例:

# -*- coding: utf-8 -*-
import scrapy
 
 
class ChoutiSpider(scrapy.Spider):
    '''
    爬去抽屜網的帖子信息
    '''
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['http://chouti.com/']
 
    def parse(self, response):
        # 獲取帖子列表的父級div
        content_div = response.xpath('//div[@id="content-list"]')
 
        # 獲取帖子item的列表
        items_list = content_div.xpath('.//div[@class="item"]')
 
        # 打開一個文件句柄,目的是為了將獲取的東西寫入文件
        with open('articles.log','a+',encoding='utf-8') as f:
            # 循環item_list
            for item in items_list:
                # 獲取每個item的第一個a標簽的文本和url鏈接
                text = item.xpath('.//a/text()').extract_first()
                href = item.xpath('.//a/@href').extract_first()
                # print(href, text.strip())
                # print('-'*100)
                f.write(href+'\n')
                f.write(text.strip()+'\n')
                f.write('-'*100+'\n')
 
        # 獲取分頁的頁碼,然后讓程序循環爬去每個鏈接
        # 頁碼標簽對象列表
        page_list = response.xpath('//div[@id="dig_lcpage"]')
        # 循環列表
        for page in page_list:
            # 獲取每個標簽下的a標簽的url,即每頁的鏈接
            page_a_url = page.xpath('.//a/@href').extract()
            # 將域名和url拼接起來
            page_url = 'https://dig.chouti.com' + page_a_url
 
            # 重要的一步!!!!
            # 導入Request模塊,然后實例化一個Request對象,然后yield它
            # 就會自動執行Request對象的callback方法,爬去的是url參數中的鏈接
            from scrapy.http import Request
            yield Request(url=page_url,callback=self.parse)

  在這個示例中,雖然我們已經通過chouti.py一個文件中的parse方法實現了爬去抽屜網的新聞並將之保存在文件中的功能,

但是我們會發現有兩個問題:

1、在循環爬去每一頁的時候,每次都需要重新打開然后再關閉文件,如果數據量龐大的話,這對性能有很大的影響。

2、我們將解析和數據持久化都放在了同一個文件的同一個方法中,沒有做到分工明確

如果要解決這兩個問題,則需要用到scrapy自動為我們生成的pipeline文件和items文件

這兩個文件怎么用

如果我們要使用這兩個文件從而解決問題,則需要有四部操作:

a.編寫pipeline文件中的類,格式如下:

class XXXPipeline(object):
	def process_item(self, item, spider):
		return item

b.編寫items文件中的類,格式如下:

class XXXItem(scrapy.Item):
	href = scrapy.Field()
	title = scrapy.Field()

c.配置settings文件

ITEM_PIPELINES = {
   'xxx.pipelines.XXXPipeline': 300,
   # 'xxx.pipelines.XXXPipeline2': 600,  # 后面的數字為優先級,數字越大,優先級月底
}

d.在parse方法中yield一個Item對象

from xxx.items import XXXItem

def parse(self, response):
    ...
    yield XXXItem(text=text,href=href)

執行流程為:

當我們在執行爬蟲中的parse方法的時候,scrapy一旦解析到有yield XXXitem的語句,就會到配置文件中找

ITEM_PIPELINES的配置項,進而找到XXXPipeline類,然后執行其中的方法,我們就可以在方法中做很多操作

當然,pipeline中不止process_item一個方法。

Pipeline中的方法詳解

class FilePipeline(object):

	def __init__(self,path):
		self.f = None
		self.path = path

	@classmethod
	def from_crawler(cls, crawler):
		"""
		初始化時候,用於創建pipeline對象
		:param crawler:
		:return:
		"""
                # 從配置文件中獲取配置好的文件存放目錄
		path = crawler.settings.get('HREF_FILE_PATH')
		return cls(path)

	def open_spider(self,spider):
		"""
		爬蟲開始執行時,調用
		:param spider:
		:return:
		"""
		self.f = open(self.path,'a+')

	def process_item(self, item, spider):
		# 在這里做持久化
		self.f.write(item['href']+'\n')
		return item  	# 交給下一個pipeline的process_item方法
		# raise DropItem()# 如果寫上這一句,后續的 pipeline的process_item方法不再執行

	def close_spider(self,spider):
		"""
		爬蟲關閉時,被調用
		:param spider:
		:return:
		"""
		self.f.close()    

去重

scrapy內部實現的去重

從上一篇的例子我們可以看出,其實scrapy內部在循環爬去頁碼的時候,已經幫我們做了去重功能的,

因為我們在首頁可以看到1,2,3,4,5,6,7,8,9,10頁的頁碼以及連接,當爬蟲爬到第二頁的時候,

還是可以看到這10個頁面及連接,然后它並沒有再重新把第一頁爬一遍。

它內部實現去重的原理是,將已爬去的網址存入一個set集合里,每次爬取新頁面的時候就先看一下是否在集合里面

如果在,就不再爬去,如果不在就爬取,然后再添加入到set里。當然,這個集合存放的不是原網址,

而是將鏈接通過request_fingerprint()方法將它變成一個類似於md5的值,這樣可以節省存儲空間

自定義去重

雖然scrapy已經幫我們實現了去重,但是有時候不足以滿足我們的需求,這樣就需要我們自定義去重了

自定義去重分兩步

1、編寫DupeFilter類

from scrapy.dupefilter import BaseDupeFilter
from scrapy.utils.request import request_fingerprint

class XXXDupeFilter(BaseDupeFilter):

	def __init__(self):
		'''初始化一個集合,用來存放爬去過的網址'''
		self.visited_fd = set()

	@classmethod
	def from_settings(cls, settings):
		'''
		如果我們自定義了DupeFilter類並且重寫了父類的該方法,
		scrapy會首先執行該方法,獲取DupeFilter對象,
		如果沒有定義,則會執行init方法來獲取對象
		'''
		return cls()

	def request_seen(self, request):
		'''在此方法中做操作,判斷以及添加網址到set里'''
		# 將request里的url轉換下,然后判斷是否在set里
		fd = request_fingerprint(request=request)
		# 循環set集合,如果已經在集合里,則返回True,爬蟲將不會繼續爬取該網址
		if fd in self.visited_fd:
			return True
		self.visited_fd.add(fd)

	def open(self):  # can return deferred
		'''開始前執行此方法'''
		print('開始')

	def close(self, reason):  # can return a deferred
		'''結束后執行此方法'''
		print('結束')

	def log(self, request, spider):  # log that a request has been filtered
		'''在此方法中可以做日志操作'''
	    print('日志')

2.配置settings文件

# 修改默認的去重規則
# DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
DUPEFILTER_CLASS = 'xxx.dupefilters.XXXDupeFilter'

深度

深度就是爬蟲所要爬取的層級

限制深度只需要配置一下即可

# 限制深度
DEPTH_LIMIT = 3

cookie

獲取上一次請求之后獲得的cookie

from scrapy.http.cookies import CookieJar

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['https://dig.chouti.com/']
    cookie_dict = {}
    def parse(self, response):

        # 去響應頭中獲取cookie,cookie保存在cookie_jar對象
        cookie_jar = CookieJar()
        cookie_jar.extract_cookies(response, response.request)

        # 去對象中將cookie解析到字典
        for k, v in cookie_jar._cookies.items():
            for i, j in v.items():
                for m, n in j.items():
                    self.cookie_dict[m] = n.value

再次請求的時候攜帶cookie

 yield Request(
            url='https://dig.chouti.com/login',
            method='POST',
            body="phone=861300000000&password=12345678&oneMonth=1",#
            cookies=self.cookie_dict,
            headers={
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
            },
            callback=self.check_login
        )

  

是不是感覺很麻煩?

那么,呵呵,其實,嘿嘿,

你只需要在Request對象的參數中加入 meta={'cookiejar': True} 即可!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM