Scrapy項目實戰


Date: 2019-07-15

Author: Sun

Scrapy是一個為了爬取網站數據、提取結構化數據而編寫的爬蟲應用框架。Scrapy內部實現了包括並發請求、免登錄、URL去重等很多復雜操作,用戶不需要明白Scrapy內部具體的爬取策略,只需要根據自己的需要,編寫小部分的代碼,就能抓取到所需要的數據

此節我們學習下如何采用采用scrapy進行項目流程開發和配置

一 項目准備工作

1. 創建爬蟲項目

使用startproject命令創建項目

scrapy startproject  scrapy_proj   #使用scrapy產生一個scrapy_name爬蟲項目

2 生成爬蟲項目

使用genspider命令在項目中創建爬蟲腳本

cd   scrapy_proj/scrapy_proj
scrapy genspider  --list
scrapy genspider myspider  "www.myspider_domain.com" 

此時會在scrapy_proj/spiders/產生一個新的文件myspider.py

這個是我們的爬取頁面的主入口和頁面下載完成后解析主入口。

Scrapy項目文件結構

  • items.py 負責數據模型的建立,類似於實體類。
  • middlewares.py 自己定義的中間件。
  • pipelines.py 負責對spider返回數據的處理。 (管道文件)
  • settings.py 負責對整個爬蟲的配置。 (項目配置)
  • spiders目錄 負責存放繼承自scrapy的爬蟲類。(寫代碼的位置)
  • scrapy.cfg scrapy基礎配置

3 項目配置

設置settings.py 文件,設置相關的配置信息,具體配置見下面參數的說明

配置文件參數說明

(1)ROBOTSTXT_OBEY = True ————— 是否遵守robots.txt規則

說明:

​ robots.txt 是遵循 Robot協議 的一個文件,它保存在網站的服務器中,它的作用是,告訴搜索引擎爬蟲,本網站哪些目錄下的網頁 不希望 你進行爬取收錄。在Scrapy啟動后,會在第一時間訪問網站的 robots.txt 文件,然后決定該網站的爬取范圍。(在某些情況下我們想要獲取的內容恰恰是被 robots.txt 所禁止訪問的。所以,某些時候,我們就要將此配置項設置為 False ,拒絕遵守 Robot協議 !)

(2)CONCURRENT_REQUESTS = 16-----------開啟線程數量,默認16,可以自行設置

​ 這個參數涉及到scrapy爬取的並發量,items的處理速度

(3)DOWNLOAD_DELAY = 3 ——— 下載延遲時間。下載器在下載同一個網站下一個頁面前需要等待的時間。該選項可以用來限制爬取速度, 減輕服務器壓力。(反爬策略之一)

(4)CONCURRENT_REQUESTS_PER_DOMAIN = 16 將對任何單個域執行的並發(即同時)請求的最大數量。

​ CONCURRENT_REQUESTS_PER_IP = 16 將對任何單個IP執行的並發(即同時)請求的最大數量。如果非零,CONCURRENT_REQUESTS_PER_DOMAIN則忽略該 設置,而改為使用此設置。換句話說,並發限制將應用於每個IP,而不是每個域。

(5)COOKIES_ENABLED = False

​ 是否啟用cookie。是否啟用cookies middleware。如果關閉,cookies將不會發送給web server。

​ 除非您真的 需要,否則請禁止cookies。在進行通用爬取時cookies並不需要, (搜索引擎則忽略cookies)。禁止cookies能減少CPU使用率及Scrapy爬蟲在內存中記錄的蹤跡,提高性能。

​ COOKIES_DEBUG:默認: False

​ 如果啟用,Scrapy將記錄所有在request(cookie 請求頭)發送的cookies及response接收到的cookies(set-cookie接收頭)

(6)AUTOTHROTTLE_START_DELAY = 5

​ 初始下載延遲時間(單位:秒)

(7)AUTOTHROTTLE_MAX_DELAY = 60

​ 高並發請求時最大延遲時間(單位:秒)

(8) USER_AGENT 用戶代理

​ 這個是至關重要的,大部分服務器在請求快了會首先檢查User_Agent,而scrapy默認的瀏覽器頭是scrapy1.1 我們需要開啟並且修改成瀏覽器頭,如:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1。 但是最好是這個USER-AGENT會隨機自動更換最好了。

(8)DEFAULT_REQUEST_HEADERS

​ 默認請求頭部信息,例如如下配置

DEFAULT_REQUEST_HEADERS = {
    'accept': 'image/webp,*/*;q=0.8',
    'accept-language': 'zh-CN,zh;q=0.8',
    'referer': 'https://www.taobao.com/',
    'user-agent': 'Mozilla/5.0 (Windows NT 6.3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36',
}

這個是瀏覽器請求頭,很多網站都會檢查客戶端的headers,比如豆瓣就是每一個請求都檢查headers的user_agent,否則只會返回403,可以開啟user-agent

(9) SPIDER_MIDDLEWARES

​ Spider中間件是介入到Scrapy中的spider處理機制的鈎子框架,可以插入自定義功能來處理發送給 Spiders 的response,以及spider產生的item和request。

要啟用Spider中間件(Spider Middlewares),可以將其加入到 SPIDER_MIDDLEWARES 設置中。 該設置是一個字典,鍵為中間件的路徑,值為中間件的順序(order)。

(10) DOWNLOADER_MIDDLEWARES

​ 要激活下載器中間件組件,將其加入到 DOWNLOADER_MIDDLEWARES 設置中。 該設置是一個字典(dict),鍵為中間件類的路徑,值為其中間件的順序(order)。

(11)ITEM_PIPELINES

每個Item Pipeline組件其實就是一個實現了一個簡單方法的Python類。他們接受一個item並在上面執行邏輯,還能決定這個item到底是否還要繼續往下傳輸,如果不要了就直接丟棄。

(12)AUTOTHROTTLE — 自動限速 (反爬策略之一)

AUTOTHROTTLE_ENABLED = True  #初始下載延遲
# The initial download delay
AUTOTHROTTLE_START_DELAY = 5   #在高延遲的情況下設置的最大下載延遲
# The maximum download delay to be set in case of high latencies
AUTOTHROTTLE_MAX_DELAY = 60    #Scrapy請求的平均數量應該並行發送每個遠程服務器
# The average number of requests Scrapy should be sending in parallel to
# each remote server
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0    
# Enable showing throttling stats for every response received:
AUTOTHROTTLE_DEBUG = False

(13)是否啟用在本地緩存,如果開啟會優先讀取本地緩存,從而加快爬取速度,視情況而定

HTTPCACHE_ENABLED = True

HTTPCACHE_EXPIRATION_SECS = 0

HTTPCACHE_DIR = 'httpcache'

HTTPCACHE_IGNORE_HTTP_CODES = []

HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

如何在scrapy中加入日志功能?

答案:在settings.py中加入如下信息

LOG_LEVEL= 'INFO'        #日志級別

LOG_FILE ='log.txt'      #日志打印的文件名稱

DEBUG < INFO < WARNING < ERROR

日志案例:(settings.py中設置如下信息)

###############   log settings begin   ######################

LOG_LEVEL = "INFO"

from datetime import datetime
import os

today = datetime.now()

LOG_DIR = "logs"
if not os.path.exists(LOG_DIR):
   os.mkdir(LOG_DIR)

LOG_FILE = "{}/scrapy_{}_{}_{}.log".format(LOG_DIR, today.year, today.month, today.day)

###############   log settings end   ######################

二。案例分析

糗事百科的主頁:www.qiushibaike.com

1 創建爬蟲項目

使用startproject命令創建項目(糗事百科爬蟲項目)

scrapy startproject  qiubai_proj   #使用scrapy產生一個scrapy_name爬蟲項目

2 生成創建爬蟲腳本

使用genspider命令在項目中創建爬蟲腳本

cd   qiubai_proj/qiubai_proj
scrapy genspider qiubai  "www.qiushibaike.com" 

此時會在qiubai_proj/spiders/產生一個新的文件qiubai.py

這個是我們的爬取頁面的主入口和頁面下載完成后解析主入口。

修改配置文件settings.py:

BOT_NAME = 'qiubai_proj'

SPIDER_MODULES = ['qiubai_proj.spiders']
NEWSPIDER_MODULE = 'qiubai_proj.spiders'


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'qiubai_proj (+http://www.yourdomain.com)'
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) " \
          "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
# Obey robots.txt rules
#ROBOTSTXT_OBEY = True
ROBOTSTXT_OBEY = False

LOG_LEVEL= 'DEBUG'

3. 數據模型

分析糗事百科網站,定義數據模型用於保存爬取的數據。

編輯文件qiubai_proj/qiubai_proj/items.py, 內容如下:

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy

class QiubaiProjItem(scrapy.Item):
    # define the fields for your item here like:
    #保存頭像鏈接
    image_url = scrapy.Field()
    #保存名字
    name = scrapy.Field()
    #保存年齡
    age = scrapy.Field()
    #保存內容
    content = scrapy.Field()
    #好笑的個數
    haha_count = scrapy.Field()

4. spiders中編寫爬取邏輯

關於xpath語法使用請參考day03中的xpath語法章節。

# -*- coding: utf-8 -*-
import scrapy


class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    allowed_domains = ['www.qiushibaike.com']
    start_urls = ['http://www.qiushibaike.com/']  #主入口url

    #parse函數就是文件解析函數,response就是響應對象
    def parse(self, response):
        # with open("qiubai.html", 'w', encoding="utf-8") as f:
        #     f.write(response.text)
        div_list = response.xpath('//div[starts-with(@id, "qiushi_tag_")]')
        print(type(div_list))
        #遍歷列表,獲取列表內容
        item_list = []
        for div in div_list: 
            '''
            先通過xpath獲取內容,返回的是一個列表
            然后通過extract()轉換成unicode字符串,再獲取第0個, 也就是指定的內容
            將解析到的內容保存到字典中
            '''
            image_url = div.xpath('./div[@class="author clearfix"]//img/@src').extract_first()
            name = div.xpath('./div[@class="author clearfix"]//h2/text()').extract_first()
            age = div.xpath('./div[@class="author clearfix"]/div/text()').extract_first()
            content = div.xpath('./a/div[@class="content"]/span/text()').extract()
            content = ' '.join(content)
            haha_count = div.xpath('./div[@class="stats"]/span[@class="stats-vote"]/i/text()').extract()[0]
            item = dict(
                image_url = image_url,
                name = name,
                age = age,
                content = content,
                haha_count = haha_count
            )
            yield item
            #item_list.append(item)
            
        #return item_list

5 運行spider

5.1 數據格式化輸出:

(1)保存爬取的內容到json文件中

​ scrapy crawl qiubai -o qiubai.json

​ 可以查看產生的json文件,將內容拷貝到json在線格式網站

https://www.json.cn/, 看數據爬取是否和真實相符。

​ (2)保存爬取的數據到xml文件中

​ scrapy crawl qiubai -o qiubai.xml

​ (3)保存爬取的數據到數據報表csv文件中

​ scrapy crawl qiubai -o qiubai.csv

知識總結:

通過指令創建爬蟲文件

   cd qiubai_proj/qiubai_proj

   scrapy genspider qiubai "www.qiushibaike.com"

   那么就會在firstSpider/firstSpider/spiders里面自動創建一個qiubai.py

      name: 爬蟲的名字,啟動的時候根據爬蟲的名字啟動項目

      allowed_domains:允許的域名,就是爬取的時候這個請求要不要發送,如果是該允許域名之下的url,就會發送,如果不是,則過濾掉這個請求,這是一個列表,可以寫多個允許的域名

      start_urls:爬蟲起始url,是一個列表,里面可以寫多個,一般只寫一個

      def parse(self, response): 這個函數非常重要,就是你以后寫代碼的地方,parse函數名是固定的,當收到下載數據的時候會自動的調用這個方法,該方法第二個參數為response,這是一個響應對象,從該對象中獲取html字符串,然后解析之。

6. 數據持久化

糗事百科的主頁:www.qiushibaike.com

本節我們將對上節數據進行數據保存(持久化)

本節分別將數據保存到json文件和mysql數據庫中

6.1 保存數據到json文件

​ 使用 Scrapy 提供的 exporter 存儲 Json 數據

​ Scrapy 為我們提供了一個 JsonItemExporter 類來進行 Json 數據的存儲,非常方便

  1. 修改上節中的spiders

    使用yield改造,使得spider成為一個生成器,不斷地往pipeline里面流入待處理的數據

# -*- coding: utf-8 -*-
import scrapy


class QiubaiSpider(scrapy.Spider):
	name = 'qiubai'
	allowed_domains = ['www.qiushibaike.com']
	start_urls = ['http://www.qiushibaike.com/']  # 主入口url

	# parse函數就是文件解析函數,response就是響應對象
	def parse(self, response):
		# with open("qiubai.html", 'w', encoding="utf-8") as f:
		#     f.write(response.text)
		div_list = response.xpath('//div[starts-with(@id, "qiushi_tag_")]')
		#print(type(div_list))
		# 遍歷列表,獲取列表內容
		item_list = []
		for div in div_list:
			'''
			先通過xpath獲取內容,返回的是一個列表
			然后通過extract()轉換成unicode字符串,再獲取第0個, 也就是指定的內容
			將解析到的內容保存到字典中
			'''
			image_url = div.xpath('./div[@class="author clearfix"]//img/@src').extract_first()
			name = div.xpath('./div[@class="author clearfix"]//h2/text()').extract_first().strip("\n")
			age = div.xpath('./div[@class="author clearfix"]/div/text()').extract_first()
			contents = div.xpath('./a/div[@class="content"]/span/text()').extract()
			content = ' '.join([ct.strip() for ct in contents])
			haha_count = div.xpath('./div[@class="stats"]/span[@class="stats-vote"]/i/text()').extract()[0]
			item = dict(
				image_url=image_url,
				name=name,
				age=age,
				content=content,
				haha_count=haha_count
			)
			yield item
			#     item_list.append(item)
			# return item_list
  1. 首先要開啟pipeline開關

在settings.py文件中開啟ITEM_PIPELINES選項,開啟如下信息

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'qiubai_proj.pipelines.QiubaiProjPipeline': 300,
}
  1. pipelines的邏輯處理

    通過pipelines將spiders轉發過來的item數據進行導入到json報表中

    編輯qiubai_proj/pipelines.py文件,添加如下內容

    # -*- coding: utf-8 -*-
    
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    from scrapy.exporters import JsonItemExporter
    
    class QiubaiProjPipeline(object):
        # 調用 scrapy 提供的 json exporter 導出 json 文件
        def __init__(self):
            self.file = open('questions_exporter.json', 'wb')
            # 初始化 exporter 實例,執行輸出的文件和編碼
            self.exporter = JsonItemExporter(self.file, encoding='utf-8', ensure_ascii=False)
            # 開啟倒入數據
            self.exporter.start_exporting()
            
            
       def close_spider(self, spider):
           self.exporter.finish_exporting()
           self.file.close()
           
           
       def process_item(self, item, spider):
           '''
           使用 scrapy.exporters.JsonItemExporter 生成的文件
           '''
           print("########### pipelines begin ###############")
           #print(item)
           self.exporter.export_item(item)
           print("########### pipelines end ###############")
           return item
    

上述pipeline將統計的item結果數據保存到questions_exporter.json中

  1. 添加日志功能

    settings.py中添加日志功能 文件及路徑,log目錄需要先建好

    today = datetime.now()
    log_file_path = "log/scrapy_{}{}{}.log".format(today.year, today.month, today.day)

日志輸出

LOG_LEVEL = 'DEBUG'
LOG_FILE = log_file_path

  1. 運行spider

​ scrapy crawl qiubai

​ 運行成功后,可以查看questions_exporter.json文件

6.2 保存數據到mysql數據庫

依賴庫:pymysql

Twisted 是一個異步網絡框架,不幸的是大部分數據庫api實現只有阻塞式接口,twisted.enterprise.adbapi為此產生,它是DB-API 2.0 API的非阻塞接口,可以訪問各種關系數據庫。

不同於直接創建數據庫連接, 而是使用 adbapi.ConnectionPool 類來管理連接. 這就可以讓 adbapi 來使用多個連接, 比如每個線程一個連接,這很簡單:

使用前面例子的 "dbmodule" , 來創建一個 ConnectionPool 對象

from twisted.enterprise import adbapi連接mysql,要這樣:

_conn = adbapi.ConnectionPool(' db='test', user='root', passwd='aaaa', host='localhost',use_unicode=True, charset='utf8'*)

  1. 數據庫定義

    創建數據庫

    create database  qiubai_db   default charset=utf8;  
    
    CREATE TABLE qiubai ( 
    
      id int(11) PRIMARY  KEY  auto_increment COMMENT '設置主鍵自增',
    
      image_url varchar(150) NOT NULL COMMENT '圖片url鏈接',
    
      name VARCHAR(50) COMMENT '名稱',
    
      age VARCHAR(10) COMMENT '年齡',
    
      content VARCHAR(500) COMMENT '內容',
    
      haha_count INT COMMENT '笑點數'
    
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

將上述內容放入到table.sql文件,進入mysql終端中,執行 source ./table.sql;

​ 創建數據庫和表成功!

  1. 修改配置文件

settings.py文件中添加如下mysql數據庫配置信息:

MYSQL_SETTINGS = {

   'HOST':'192.168.51.63',    #數據庫地址
   'DATABASE':'qiubai_proj',    #數據庫名稱
   'USER':'zhougy',           #登陸用戶名
   'PASSWORD':'123456',       #登陸密碼
   'CHARSET':'utf8',          #字符編碼

}
  1. 添加數據庫操作的pipeline

添加文件 mysql_pipelines.py, 內容添加如下:

scrapy框架底層網絡庫是twisted,同時他也

此文件采用twisted.enterprise.adbapi異步處理接口處理mysql操作

# -*- coding: utf-8 -*-  
__author__ = 'zhougy'
__date__ = '2018/9/2 下午7:26'
import json
import pymysql
from twisted.enterprise import adbapi
from .settings import MYSQL_SETTINGS
import logging

class MysqlTwistedPipline(object):
	def __init__(self, dbpool):
		self.dbpool = dbpool

	@classmethod
	def from_settings(cls, settings):
		dbparms = dict(
			host=MYSQL_SETTINGS['HOST'],
			db=MYSQL_SETTINGS['DATABASE'],
			user=MYSQL_SETTINGS['USER'],
			passwd=MYSQL_SETTINGS['PASSWORD'],
			charset=MYSQL_SETTINGS['CHARSET'],
			cursorclass=pymysql.cursors.DictCursor,  # 指定 curosr 類型
			use_unicode=True,
		)
		# 指定擦做數據庫的模塊名和數據庫參數參數
		dbpool = adbapi.ConnectionPool("pymysql", **dbparms)
		return cls(dbpool)

	def process_item(self, item, spider):
		'''
		使用twisted將mysql插入變成異步處理
		:param item:
		:param spider:
		:return:
		'''
		query = self.dbpool.runInteraction(self.db_insert, item)
		# 指定異常處理方法
		query.addErrback(self.handle_error, item, spider)  # 處理異常
		return item
		
	def handle_error(self, failure, item, spider):
		# 處理異步插入的異常
		print("######################1")
		#print(item)
		#print(failure)
		logging.error(f"handler_error has error failure: {failure}")
		logging.warning()
		print("########################2")	
		
	def db_insert(self, cursor, item):
		# 執行具體的插入
		# 根據不同的item 構建不同的sql語句並插入到mysql中
		insert_sql, params = self.get_insert_sql(item)
		cursor.execute(insert_sql, params)
		logging.info(f"write db ok with data:{item['name']}")
		
	def get_insert_sql(self, item):
		insert_sql = """
		                 insert into qiubai(image_url, name, age, content, haha_count)
		                 VALUES (%s, %s, %s, %s, %s)
		             """
		params = (
			item["image_url"], item["name"], item["age"], item["content"],
			item["haha_count"])
		return insert_sql, params

​ 至此,基於mysql的pipeline異步處理管道邏輯就已經完成。

修改配置文件settings.py

將上述MysqlTwistedPipline管道處理類添加到配置文件中

ITEM_PIPELINES = {

'qiubai_proj.pipelines.QiubaiProjPipeline': 300,

   'qiubai_proj.mysql_pipelines.MysqlTwistedPipline': 300,
}

運行啟動spider

scrapy crawl qiubai

運行成功后,可以查看數據庫表數據。


免責聲明!

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



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