Python Scrapy 爬蟲(二):scrapy 初試

接上篇,以前咱們搭建好了運行環境,至關於咱們搭好了炮臺,如今就差獵物和武器了。html

1、選取獵物

此處選擇爬取西刺代理 IP 做爲示例項目,緣由有以下兩點:python

  • 西刺代理數據規範,爬取簡單,做爲演示項目比較合適
  • 代理 IP 在咱們的爬蟲中也許還能派上用場(雖然可用率低了點,但若是你不是走量的,平時本身用一下仍是不錯的)

獵物 URLmysql

http://www.xicidaili.com/nn
複製代碼

注:雖然西刺聲稱提供了全網惟一的免費代理 IP 接口,但彷佛並無什麼用,由於根本不返回數據...咱們本身作點小工做仍是能夠的。git

2、咱們的目標是

scrapy 初試計劃實現的效果是:github

  • 從西刺代理網站上爬取免費的國內高匿代理 IP
  • 將爬取的代理 IP存入 MySQL 數據庫中
  • 經過循環爬取的方式獲取最新 IP,並經過設立數據庫惟一鍵的方式進行簡易版去重

3、目標分析

  正所謂知己知彼,至於勝多勝少,先不糾結。咱們先打開網站(使用 Chrome 打開),看見的大概是下面的這個東西。web

西刺代理

網頁結構分析sql

  • 大體瀏覽咱們的目標網站,選取咱們須要的數據。從網頁上咱們能夠看到西刺代理國內高匿 IP 展現了國家、IP、端口、服務器地址、是否匿名、類型、速度、鏈接時間、存活時間、驗證時間這些信息。
  • 在網頁的數據展現區的字段名稱(藍色)區域點擊右鍵 -> 檢查,咱們發現該網頁的數據是由
    進行渲染布局的
  • 把網頁拖動到底部,發現網站的數據進行分佈展現,咱們點擊下一頁翻頁一觀察就發現頗有規律的是頁碼參數就在 URL 的後面,當前第幾頁就傳數字幾,如:
http://www.xicidaili.com/nn/3
複製代碼
  • 咱們看到國家是顯示的國旗,速度與鏈接時間是顯示的兩個顏色塊,彷佛不太好拿這三個信息?

  此時,咱們將鼠標移至網頁中某一面小國旗的位置處點擊右鍵 -> 檢查,咱們發現這是一個 img 標籤,其中 alt 屬性有國家代碼。明瞭了吧,這個國家信息咱們能拿到。chrome

  咱們再把鼠標移至速度的色塊處點擊右鍵 -> 檢查,咱們能夠發現有個 div 上有個 title 顯示相似 0.876秒這種數據,而鏈接時間也是這個套路,因而基本肯定咱們的數據項都能拿到,且能經過翻頁拿取更多 IP。數據庫

4、準備武器彈藥

4.1 準備武器

咱們的武器固然是 scrapywindows

4.2 準備彈藥

彈藥包括

  • pymsql (Python 操做 MySQL 的庫)
  • fake-useragent (隱藏你的身份)
  • pywin32

5、開火

5.1 建立虛擬環境

C:\Users\jiang>workon

Pass a name to activate one of the following virtualenvs:
==============================================================================
test
C:\Users\jiang>mkvirtualenv proxy_ip
Using base prefix 'd:\\program files\\python36'
New python executable in D:\ProgramData\workspace\python\env\proxy_ip\Scripts\python.exe
Installing setuptools, pip, wheel...done.
複製代碼

5.2 安裝第三方庫

注意:下文的命令中,只要命令前方有 "(proxy_ip)" 標識,即表示該命令是在上面建立的 proxy_ip 的 python 虛擬環境下執行的。

1 安裝 scrapy

(proxy_ip) C:\Users\jiang>pip install scrapy -i https://pypi.douban.com/simple
複製代碼

若是安裝 scrapy 時出現以下錯誤:

error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
複製代碼

可以使用以下方式解決:

  • 手動下載 twisted 的 whl 包,下載地址以下
https://www.lfd.uci.edu/~gohlke/pythonlibs/
複製代碼

打開上方的 URL,搜索 Twisted,下載最新的符合 python 版本與 windows 版本的 whl 文件,如

twisted

如上圖所示,Twisted‑18.4.0‑cp36‑cp36m‑win_amd64.whl 表示匹配 Python 3.6 的 Win 64 文件。這是與我環境匹配的。所以,下載該文件,而後找到文件下載的位置,把其拖動到 CMD 命令行窗口進行安裝,以下示例:

(proxy_ip) C:\Users\jiang>pip install D:\ProgramData\Download\Twisted-18.4.0-cp36-cp36m-win_amd64.whl
複製代碼

離線安裝 twisted

再執行以下命令安裝 scrapy 便可成功安裝

(proxy_ip) C:\Users\jiang>pip install scrapy -i https://pypi.douban.com/simple
複製代碼

2 安裝 pymysql

(proxy_ip) C:\Users\jiang>pip install pymysql -i https://pypi.douban.com/simple
複製代碼

3 安裝 fake-useragent

(proxy_ip) C:\Users\jiang>pip install fake-useragent -i https://pypi.douban.com/simple
複製代碼

4 安裝 pywin32

(proxy_ip) C:\Users\jiang>pip install pywin32 -i https://pypi.douban.com/simple
複製代碼

5.3 建立 scrapy 項目

1 建立一個工做目錄(可選)

咱們能夠建立一個專門的目錄用於存放 python 的項目文件,例如:

我在用戶目錄下建立了一個 python_projects,也能夠建立任何名稱的目錄或者選用一個本身知道位置的目錄。

(proxy_ip) C:\Users\jiang>mkdir python_projects
複製代碼

2 建立 scrapy 項目

進入工做目錄,執行命令建立一個 scrapy 的項目

(proxy_ip) C:\Users\jiang>cd python_projects

(proxy_ip) C:\Users\jiang\python_projects>scrapy startproject proxy_ip
New Scrapy project 'proxy_ip', using template directory 'd:\\programdata\\workspace\\python\\env\\proxy_ip\\lib\\site-packages\\scrapy\\templates\\project', created in:
    C:\Users\jiang\python_projects\proxy_ip

You can start your first spider with:
    cd proxy_ip
    scrapy genspider example example.com
複製代碼

至此,已經完成了建立一個 scrapy 項目的工做。接下來,開始咱們的狩獵計劃吧...

5.4 關鍵配置編碼

1 打開項目

使用 PyCharm 打開咱們剛剛建立好的 scrapy 項目,點擊 "Open in new window" 打開項目

打開項目

scrapy 項目初始結構

2 配置項目環境

File -> Settings -> Project: proxy_ip -> Project Interpreter -> 齒輪按鈕 -> add ...

設置項目環境

選擇 "Existing environment" -> "..." 按鈕

選擇虛擬環境位置

找到以前建立的 proxy_ip 的虛擬環境的 Scripts/python.exe,選中並肯定

選擇 proxy_ip 虛擬環境的 python.exe

虛擬環境 proxy_ip 的位置默認位於 C:\Users\username\envs,此處個人虛擬機位置已經經過修改 WORK_ON 環境變量更改。

3 配置 items.py

items 中定義了咱們爬取的字段以及對各字段的處理,能夠簡單地相似理解爲這是一個 Excel 的模板,咱們定義了模塊的表頭字段及字段的屬性等等,而後咱們按照這個模塊往表格裏填數。

class ProxyIpItem(scrapy.Item):
    country = scrapy.Field()
    ip = scrapy.Field()
    port = scrapy.Field( )
    server_location = scrapy.Field()
    is_anonymous = scrapy.Field()
    protocol_type = scrapy.Field()
    speed = scrapy.Field()
    connect_time = scrapy.Field()
    survival_time = scrapy.Field()
    validate_time = scrapy.Field()
    source = scrapy.Field()
    create_time = scrapy.Field()

    def get_insert_sql(self):
        insert_sql = """ insert into proxy_ip( country, ip, port, server_location, is_anonymous, protocol_type, speed, connect_time, survival_time, validate_time, source, create_time ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """

        params = (
                    self["country"], self["ip"], self["port"], self["server_location"],
                    self["is_anonymous"], self["protocol_type"], self["speed"], self["speed"],
                    self["survival_time"], self["validate_time"], self["source"], self["create_time"]
                  )
        return insert_sql, params
複製代碼

4 編寫 pipelines.py

  pipeline 直譯爲管道,而在 scrapy 中的 pipelines 的功能與管道也很是類似,咱們能夠在 piplelines.py 中定義多個管道。你能夠這麼來理解,好比:有一座水庫,咱們要從這個水庫來取水到不一樣的地方,好比自來水廠、工廠、農田...

  因而咱們建了三條管道一、二、3,分別鏈接到自來水廠,工廠、農田。當管道創建好以後,只要咱們須要水的時候,打開開關(閥門),水庫裏的水就能源源不斷的流向不一樣的目的地。

  而此處的場景與上面的水庫取水有不少類似性。好比,咱們要爬取的網站就至關於這個水庫,而咱們在 pipelines 中創建的管道就至關於創建的取水管道,只不過 pipelines 中定義的管道不是用來取水,而是用來存取咱們爬取的數據,好比,你能夠在 pipelines 中定義一個流向文件的管道,也能夠創建一個流向數據庫的管道(好比MySQL/Mongodb/ElasticSearch 等等),而這些管道的閥門就位於 settings.py 文件中,固然,你也能夠多個閥門同時打開,你甚至還能夠定義各管道的優先級。

  若是您能看到這兒,您是否會以爲頗有意思。原來,那些程序界的大佬們,他們在設計這個程序框架的時候,其實參照了不少現實生活中的實例。在此,我雖然不敢確定編寫出 scrapy 這樣優秀框架的前輩們是否是參考的現實生活中的水庫的例子在設計整個框架,但我能肯定的是他們必定參考了和水庫模型相似的場景。

  在此,我也向這些前輩致敬,他們設計的框架很是優秀並且簡單好用,感謝!

前面說了這麼多,都是個人一些我的理解以及感觸,下面給出 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

import pymysql
from twisted.enterprise import adbapi


class ProxyIpPipeline(object):
    """ xicidaili """
    def __init__(self, dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_settings(cls, settings):
        dbparms = dict(
            host=settings["MYSQL_HOST"],
            db=settings["MYSQL_DBNAME"],
            user=settings["MYSQL_USER"],
            passwd=settings["MYSQL_PASSWORD"],
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
            use_unicode=True,
        )
        dbpool = adbapi.ConnectionPool("pymysql", **dbparms)
        # 實例化一個對象
        return cls(dbpool)

    def process_item(self, item, spider):
        # 使用twisted將mysql插入變成異步執行
        query = self.dbpool.runInteraction(self.do_insert, item)
        query.addErrback(self.handle_error, item, spider)  # 處理異常


    def handle_error(self, failure, item, spider):
        # 處理異步插入的異常
        print(failure)


    def do_insert(self, cursor, item):
        # 執行具體的插入
        # 根據不一樣的item 構建不一樣的sql語句並插入到mysql中
        insert_sql, params = item.get_insert_sql()
        print(insert_sql, params)
        try:
            cursor.execute(insert_sql, params)
        except Exception as e:
            print(e)
複製代碼

注:

  • 以上代碼定義的一個管道,是一個注入 MySQL 的管道,其中的寫法模式徹底固定,只有其中的 dbpool = adbapi.ConnectionPool("pymysql", **dbparms) 此處須要與你使用的鏈接 mysql 的第三方庫一致,好比此處我使用的是 pymysql,就設置 pymysql,若是使用的是其餘第三方庫,如 MySQLdb,更改成 MySQLdb 便可...
  • 其中的 dbparams 中的 mysql 信息,是從 settings 文件中讀取的,咱們只須要配置 settings 文件中的 MySQL 信息便可

配置 settings.py 中的 pipeline 設置

因爲咱們的 pipelines.py 中定義的這惟一一條管道的管道名稱是建立 scrapy 項目時默認的。所以,settings.py 文件中已經默認了會使用該管道。

pipelines 默認設置

在此爲了突出演示效果,我在下方列舉了默認的 pipeline 與 其餘自定義的 pipeline 的設置

pipeline 自定義設置

上面表示對於咱們設置了兩個 pipeline,其中一個是寫入 MySQL,而另外一個是寫入 MongoDB,其中後面的 300,400 表示 pipeline 的執行優先級,其中數值越大優先級越小。

因爲 pipelines 中用到的 MySQL 信息在 settings 中配置,在此也列舉出,在 settings.py 文件的末尾添加以下信息便可

MYSQL_HOST = "localhost"
MYSQL_DBNAME = "crawler"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root123"
複製代碼

settings 中的 MySQL 設置

5 編寫 spider

  接着使用水庫放水的場景做爲示例,水庫放水給下游,但並非水庫裏的全部東西都須要,好比水庫裏有雜草,有泥石等等,這些東西若是不須要,那麼就要把它過濾掉。而過濾的過程就相似於咱們的 spider 的處理過程。

  前面的配置都是輔助型的,spider 裏面纔是咱們爬取數據的邏輯,在 spiders 目錄下建立一個 xicidaili.py 的文件,在裏面編寫咱們須要的 spider 邏輯。

  因爲本文篇幅已通過於冗長,此處我打算省略 spider 中的詳細介紹,能夠從官網獲取到 spider 相關信息,其中官方首頁就有一個簡單的 spider 文件的標準模板,而咱們要作的是,只須要按照模板,在其中編寫咱們提取數據的規則便可。

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

from scrapy.http import Request
from proxy_ip.items import ProxyIpItem
from proxy_ip.util import DatetimeUtil


class ProxyIp(scrapy.Spider):
    name = 'proxy_ip'
    allowed_domains = ['www.xicidaili.com']
    # start_urls = ['http://www.xicidaili.com/nn/1']

    def start_requests(self):
        start_url = 'http://www.xicidaili.com/nn/'

        for i in range(1, 6):
            url = start_url + str(i)
            yield Request(url=url, callback=self.parse)

    def parse(self, response):
        ip_table = response.xpath('//table[@id="ip_list"]/tr')
        proxy_ip = ProxyIpItem()

        for tr in ip_table[1:]:
            # 提取內容列表
            country = tr.xpath('td[1]/img/@alt')
            ip = tr.xpath('td[2]/text()')
            port = tr.xpath('td[3]/text()')
            server_location = tr.xpath('td[4]/a/text()')
            is_anonymous = tr.xpath('td[5]/text()')
            protocol_type = tr.xpath('td[6]/text()')
            speed = tr.xpath('td[7]/div[1]/@title')
            connect_time = tr.xpath('td[8]/div[1]/@title')
            survival_time = tr.xpath('td[9]/text()')
            validate_time = tr.xpath('td[10]/text()')

            # 提取目標內容
            proxy_ip['country'] = country.extract()[0].upper() if country else ''
            proxy_ip['ip'] = ip.extract()[0] if ip else ''
            proxy_ip['port'] = port.extract()[0] if port else ''
            proxy_ip['server_location'] = server_location.extract()[0] if server_location else ''
            proxy_ip['is_anonymous'] = is_anonymous.extract()[0] if is_anonymous else ''
            proxy_ip['protocol_type'] = protocol_type.extract()[0] if type else ''
            proxy_ip['speed'] = speed.extract()[0] if speed else ''
            proxy_ip['connect_time'] = connect_time.extract()[0] if connect_time else ''
            proxy_ip['survival_time'] = survival_time.extract()[0] if survival_time else ''
            proxy_ip['validate_time'] = '20' + validate_time.extract()[0] + ':00' if validate_time else ''
            proxy_ip['source'] = 'www.xicidaili.com'
            proxy_ip['create_time'] = DatetimeUtil.get_current_localtime()

            yield proxy_ip
複製代碼

6 middlewares.py

  不少狀況下,咱們只須要編寫或配置 items,pipeline,spidder,settings 這四個部分便可完整運行一個完整的爬蟲項目,但 middlewares 在少數狀況下會有用到。

  再用水庫放水的場景爲例,默認狀況下,水庫放水的流程大概是,自來水廠須要用水,因而他們發起一個請求給水庫,水庫收到請求後把閥門打開,按照過濾後的要求把水放給下游。但若是自來水廠有特殊要求,好比說自來水廠他能夠只想要天天 00:00 - 7:00 這段時間放水,這就屬於自定義狀況了。

  而 middlewares.py 中就是定義的這些信息,它包括默認的請求與響應處理,好比默認全天放水... 而若是咱們有特殊需求,在 middlewares.py 定義便可... 如下附本項目中使用 fake-useragent 來隨機切換請求的 user-agent 的代碼:

class RandomUserAgentMiddleware(object):
    """ 隨機更換 user-agent """
    def __init__(self, crawler):
        super(RandomUserAgentMiddleware, self).__init__()
        self.ua = UserAgent()
        self.ua_type = crawler.settings.get("RANDOM_UA_TYPE", "random")

 @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler)

    def process_request(self, request, spider):
        def get_ua():
            return getattr(self.ua, self.ua_type)
        random_ua = get_ua()
        print("current using user-agent: " + random_ua)
        request.headers.setdefault("User-Agent", random_ua)
複製代碼

settings.py 中配置 middleware 信息

# Crawl responsibly by identifying yourself (and your website) on the user-agent
RANDOM_UA_TYPE = "random"  # 能夠配置 {'ie', 'chrome', 'firefox', 'random'...}

# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
    'proxy_ip.middlewares.RandomUserAgentMiddleware': 100,
}
複製代碼

7 settings.py

settings.py 中配置了項目的不少信息,用於統一管理配置,下方給出示例:

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

import os
import sys
# Scrapy settings for proxy_ip project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
# http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'proxy_ip'

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


# Crawl responsibly by identifying yourself (and your website) on the user-agent
RANDOM_UA_TYPE = "random"  # 能夠配置 {'ie', 'chrome', 'firefox', 'random'...}

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 10
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}

# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'proxy_ip.middlewares.ProxyIpSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
    'proxy_ip.middlewares.RandomUserAgentMiddleware': 100,
}

# Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}

# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'proxy_ip.pipelines.ProxyIpPipeline': 300,
}

BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'proxy_ip'))


# Enable and configure the AutoThrottle extension (disabled by default)
# See http://doc.scrapy.org/en/latest/topics/autothrottle.html
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
# 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 = True

# Enable and configure HTTP caching (disabled by default)
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'


MYSQL_HOST = "localhost"
MYSQL_DBNAME = "crawler"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root123"
複製代碼

6、運行測試

在 proxy_ip 項目的根目錄下,建立一個 main.py 做爲項目運行的入口文件,其中代碼以下:

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

__author__ = 'jiangzhuolin'

import sys
import os
import time

while True:
    os.system("scrapy crawl proxy_ip")  # scrapy spider 的啓動方法 scrapy crawl spider_name
    print("程序開始休眠...")
    time.sleep(3600)  # 休眠 1 小時後繼續爬取

複製代碼

右鍵 "run main" 查看運行效果

程序運行效果

7、總結

  個人原來打算是寫一篇 scrapy 簡單項目的詳細介紹,把裏面各類細節都經過我的的理解分享出來。但很是遺憾,因爲經驗不足,致使越寫越以爲篇幅會過於冗長。所以裏面有大量信息被我簡化或者直接沒有寫出來,本文可能不適合徹底小白的新手,若是你寫過簡單的 scrapy 項目,但對其中的框架不甚理解,我但願能經過本文有所改善。

  本項目代碼已提交到我我的的 github 與 碼雲上,若是訪問 github 較慢,能夠訪問碼雲獲取完整代碼,其中,包括了建立 MySQL 數據庫表的 SQL 代碼

github 代碼地址:github.com/jiangzhuoli…

碼雲代碼地址:gitee.com/jzl975/prox…

相關文章
相關標籤/搜索