scrapy與redis實戰

從零搭建Redis-Scrapy分佈式爬蟲

Scrapy-Redis分佈式策略:

假設有四臺電腦:Windows 十、Mac OS X、Ubuntu 16.0四、CentOS 7.2,任意一臺電腦均可以做爲 Master端 或 Slaver端,好比:css

  • Master端(核心服務器) :使用 Windows 10,搭建一個Redis數據庫,不負責爬取,只負責url指紋判重、Request的分配,以及數據的存儲html

  • Slaver端(爬蟲程序執行端) :使用 Mac OS X 、Ubuntu 16.0四、CentOS 7.2,負責執行爬蟲程序,運行過程當中提交新的Request給Masterpython

  1. 首先Slaver端從Master端拿任務(Request、url)進行數據抓取,Slaver抓取數據的同時,產生新任務的Request便提交給 Master 處理;mysql

  2. Master端只有一個Redis數據庫,負責將未處理的Request去重和任務分配,將處理後的Request加入待爬隊列,而且存儲爬取的數據。git

Scrapy-Redis默認使用的就是這種策略,咱們實現起來很簡單,由於任務調度等工做Scrapy-Redis都已經幫咱們作好了,咱們只須要繼承RedisSpider、指定redis_key就好了。github

缺點是,Scrapy-Redis調度的任務是Request對象,裏面信息量比較大(不只包含url,還有callback函數、headers等信息),可能致使的結果就是會下降爬蟲速度、並且會佔用Redis大量的存儲空間,因此若是要保證效率,那麼就須要必定硬件水平web

1、安裝Redis

安裝Redis:http://redis.io/downloadredis

安裝完成後,拷貝一份Redis安裝目錄下的redis.conf到任意目錄,建議保存到:/etc/redis/redis.conf(Windows系統能夠無需變更)sql

2、修改配置文件 redis.conf

打開你的redis.conf配置文件,示例:mongodb

  • 非Windows系統: sudo vi /etc/redis/redis.conf

  • Windows系統:C:\Intel\Redis\conf\redis.conf

  1. Master端redis.conf裏註釋bind 127.0.0.1,Slave端才能遠程鏈接到Master端的Redis數據庫。

  2.daemonize yno表示Redis默認不做爲守護進程運行,即在運行redis-server /etc/redis/redis.conf時,將顯示Redis啓動提示畫面;

    • daemonize yes則默認後臺運行,沒必要從新啓動新的終端窗口執行其餘命令,看我的喜愛和實際須要。

3、測試Slave端遠程鏈接Master端

測試中,Master端Windows 10 的IP地址爲:192.168.199.108

  1. Master端按指定配置文件啓動 redis-server,示例:

    • 非Windows系統:sudo redis-server /etc/redis/redis.conf

    • Windows系統:命令提示符(管理員)模式下執行 redis-server C:\Intel\Redis\conf\redis.conf讀取默認配置便可。

  2. Master端啓動本地redis-cli

         3.slave端啓動redis-cli -h 192.168.199.108-h 參數表示鏈接到指定主機的redis數據庫

注意:Slave端無需啓動redis-server,Master端啓動便可。只要 Slave 端讀取到了 Master 端的 Redis 數據庫,則表示可以鏈接成功,能夠實施分佈式。

4、Redis數據庫桌面管理工具

這裏推薦 Redis Desktop Manager,支持 Windows、Mac OS X、Linux 等平臺:

下載地址:https://redisdesktop.com/download 

源碼自帶項目說明:

使用scrapy-redis的example來修改

先從github上拿到scrapy-redis的示例,而後將裏面的example-project目錄移到指定的地址:

# clone github scrapy-redis源碼文件
git clone https://github.com/rolando/scrapy-redis.git

# 直接拿官方的項目範例,更名爲本身的項目用(針對懶癌患者)
mv scrapy-redis/example-project ~/scrapyredis-project

咱們clone到的 scrapy-redis 源碼中有自帶一個example-project項目,這個項目包含3個spider,分別是dmoz, myspider_redis,mycrawler_redis

1、dmoz (class DmozSpider(CrawlSpider))

這個爬蟲繼承的是CrawlSpider,它是用來講明Redis的持續性,當咱們第一次運行dmoz爬蟲,而後Ctrl + C停掉以後,再運行dmoz爬蟲,以前的爬取記錄是保留在Redis裏的。

分析起來,其實這就是一個 scrapy-redis 版 CrawlSpider 類,須要設置Rule規則,以及callback不能寫parse()方法。

執行方式:scrapy crawl dmoz

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class DmozSpider(CrawlSpider):
    """Follow categories and extract links."""
    name = 'dmoz'
    allowed_domains = ['dmoz.org']
    start_urls = ['http://www.dmoz.org/']

    rules = [
        Rule(LinkExtractor(
            restrict_css=('.top-cat', '.sub-cat', '.cat-item')
        ), callback='parse_directory', follow=True),
    ]

    def parse_directory(self, response):
        for div in response.css('.title-and-desc'):
            yield {
                'name': div.css('.site-title::text').extract_first(),
                'description': div.css('.site-descr::text').extract_first().strip(),
                'link': div.css('a::attr(href)').extract_first(),
            } 

2、myspider_redis (class MySpider(RedisSpider))

這個爬蟲繼承了RedisSpider, 它可以支持分佈式的抓取,採用的是basic spider,須要寫parse函數。

其次就是再也不有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis裏pop出來,成爲請求的url地址。

from scrapy_redis.spiders import RedisSpider

class MySpider(RedisSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'myspider_redis'

    # 注意redis-key的格式:
    redis_key = 'myspider:start_urls'

    # 可選:等效於allowd_domains(),__init__方法按規定格式寫,使用時只須要修改super()裏的類名參數便可
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這裏的類名爲當前類名
        super(MySpider, self).__init__(*args, **kwargs)

    def parse(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意:

RedisSpider類 不須要寫allowd_domainsstart_urls

  1. scrapy-redis將從在構造方法__init__()裏動態定義爬蟲爬取域範圍,也能夠選擇直接寫allowd_domains

  2. 必須指定redis_key,即啓動爬蟲的命令,參考格式:redis_key = 'myspider:start_urls'

  3. 根據指定的格式,start_urls將在 Master端的 redis-cli 裏 lpush 到 Redis數據庫裏,RedisSpider 將在數據庫裏獲取start_urls。

執行方式:

  1. 經過runspider方法執行爬蟲的py文件(也能夠分次執行多條),爬蟲(們)將處於等待準備狀態:

    scrapy runspider myspider_redis.py

  2. 在Master端的redis-cli輸入push指令,參考格式:

    $redis > lpush myspider:start_urls http://www.dmoz.org/

  3. Slaver端爬蟲獲取到請求,開始爬取。

3、mycrawler_redis (class MyCrawler(RedisCrawlSpider))

這個RedisCrawlSpider類爬蟲繼承了RedisCrawlSpider,可以支持分佈式的抓取。由於採用的是crawlSpider,因此須要遵照Rule規則,以及callback不能寫parse()方法。

一樣也再也不有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis裏pop出來,成爲請求的url地址。

from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor

from scrapy_redis.spiders import RedisCrawlSpider

class MyCrawler(RedisCrawlSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'mycrawler_redis'
    redis_key = 'mycrawler:start_urls'

    rules = (
        # follow all links
        Rule(LinkExtractor(), callback='parse_page', follow=True),
    )

    # __init__方法必須按規定寫,使用時只須要修改super()裏的類名參數便可
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這裏的類名爲當前類名
        super(MyCrawler, self).__init__(*args, **kwargs)

    def parse_page(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        } 

注意:

一樣的,RedisCrawlSpider類不須要寫allowd_domainsstart_urls

  1. scrapy-redis將從在構造方法__init__()裏動態定義爬蟲爬取域範圍,也能夠選擇直接寫allowd_domains

  2. 必須指定redis_key,即啓動爬蟲的命令,參考格式:redis_key = 'myspider:start_urls'

  3. 根據指定的格式,start_urls將在 Master端的 redis-cli 裏 lpush 到 Redis數據庫裏,RedisSpider 將在數據庫裏獲取start_urls。

執行方式:

  1. 經過runspider方法執行爬蟲的py文件(也能夠分次執行多條),爬蟲(們)將處於等待準備狀態:

    scrapy runspider mycrawler_redis.py

  2. 在Master端的redis-cli輸入push指令,參考格式:

    $redis > lpush mycrawler:start_urls http://www.dmoz.org/

  3. 爬蟲獲取url,開始執行。

總結:

  1. 若是只是用到Redis的去重和保存功能,就選第一種;

  2. 若是要寫分佈式,則根據狀況,選擇第二種、第三種

  3. 一般狀況下,會選擇用第三種方式編寫深度聚焦爬蟲

有緣網分佈式爬蟲案例:

# clone github scrapy-redis源碼文件
git clone https://github.com/rolando/scrapy-redis.git

# 直接拿官方的項目範例,更名爲本身的項目用(針對懶癌患者)
mv scrapy-redis/example-project ~/scrapy-youyuan 

修改settings.py

下面列舉了修改後的配置文件中與scrapy-redis有關的部分,middleware、proxy等內容在此就省略了。

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

# 指定使用scrapy-redis的調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilters.RFPDupeFilter'

# 指定排序爬取地址時使用的隊列,
# 默認的 按優先級排序(Scrapy默認),由sorted set實現的一種非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
# 可選的 按先進先出排序(FIFO)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderQueue'
# 可選的 按後進先出排序(LIFO)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderStack'

# 在redis中保持scrapy-redis用到的各個隊列,從而容許暫停和暫停後恢復,也就是不清理redis queues
SCHEDULER_PERSIST = True

# 只在使用SpiderQueue或者SpiderStack是有效的參數,指定爬蟲關閉的最大間隔時間
# SCHEDULER_IDLE_BEFORE_CLOSE = 10

# 經過配置RedisPipeline將item寫入key爲 spider.name : items 的redis的list中,供後面的分佈式處理item
# 這個已經由 scrapy-redis 實現,不須要咱們寫代碼
ITEM_PIPELINES = {
    'example.pipelines.ExamplePipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400
}

# 指定redis數據庫的鏈接參數
# REDIS_PASS是我本身加上的redis鏈接密碼(默認不作)
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
#REDIS_PASS = 'redisP@ssw0rd'

# LOG等級
LOG_LEVEL = 'DEBUG'

#默認狀況下,RFPDupeFilter只記錄第一個重複請求。將DUPEFILTER_DEBUG設置爲True會記錄全部重複的請求。
DUPEFILTER_DEBUG =True

# 覆蓋默認請求頭,能夠本身編寫Downloader Middlewares設置代理和UserAgent
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8',
    'Connection': 'keep-alive',
    'Accept-Encoding': 'gzip, deflate, sdch'
}

查看pipeline.py

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

from datetime import datetime

class ExamplePipeline(object):
    def process_item(self, item, spider):
        #utcnow() 是獲取UTC時間
        item["crawled"] = datetime.utcnow()
        # 爬蟲名
        item["spider"] = spider.name
        return item

修改items.py

增長咱們最後要保存的youyuanItem項,這裏只寫出來一個很是簡單的版本

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

from scrapy.item import Item, Field

class youyuanItem(Item):
    # 我的頭像連接
    header_url = Field()
    # 用戶名
    username = Field()
    # 心裏獨白
    monologue = Field()
    # 相冊圖片連接
    pic_urls = Field()
    # 年齡
    age = Field()

    # 網站來源 youyuan
    source = Field()
    # 我的主頁源url
    source_url = Field()

    # 獲取UTC時間
    crawled = Field()
    # 爬蟲名
    spider = Field()

編寫 spiders/youyuan.py

在spiders目錄下增長youyuan.py文件編寫咱們的爬蟲,以後就能夠運行爬蟲了。 這裏的提供一個簡單的版本:

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

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
# 使用redis去重
from scrapy.dupefilters import RFPDupeFilter

from example.items import youyuanItem
import re

#
class YouyuanSpider(CrawlSpider):
    name = 'youyuan'
    allowed_domains = ['youyuan.com']
    # 有緣網的列表頁
    start_urls = ['http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p1/']

    # 搜索頁面匹配規則,根據response提取連接
    list_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/find/.+'))

    # 北京、18~25歲、女性 的 搜索頁面匹配規則,根據response提取連接
    page_lx = LinkExtractor(allow =(r'http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p\d+/'))

    # 我的主頁 匹配規則,根據response提取連接
    profile_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/\d+-profile/'))

    rules = (
        # 匹配find頁面,跟進連接,跳板
        Rule(list_page_lx, follow=True),

        # 匹配列表頁成功,跟進連接,跳板
        Rule(page_lx, follow=True),

        # 匹配我的主頁的連接,造成request保存到redis中等待調度,一旦有響應則調用parse_profile_page()回調函數處理,不作繼續跟進
        Rule(profile_page_lx, callback='parse_profile_page', follow=False),
    )

    # 處理我的主頁信息,獲得咱們要的數據
    def parse_profile_page(self, response):
        item = youyuanItem()
        item['header_url'] = self.get_header_url(response)
        item['username'] = self.get_username(response)
        item['monologue'] = self.get_monologue(response)
        item['pic_urls'] = self.get_pic_urls(response)
        item['age'] = self.get_age(response)
        item['source'] = 'youyuan'
        item['source_url'] = response.url

        #print "Processed profile %s" % response.url
        yield item


    # 提取頭像地址
    def get_header_url(self, response):
        header = response.xpath('//dl[@class=\'personal_cen\']/dt/img/@src').extract()
        if len(header) > 0:
            header_url = header[0]
        else:
            header_url = ""
        return header_url.strip()

    # 提取用戶名
    def get_username(self, response):
        usernames = response.xpath("//dl[@class=\'personal_cen\']/dd/div/strong/text()").extract()
        if len(usernames) > 0:
            username = usernames[0]
        else:
            username = "NULL"
        return username.strip()

    # 提取心裏獨白
    def get_monologue(self, response):
        monologues = response.xpath("//ul[@class=\'requre\']/li/p/text()").extract()
        if len(monologues) > 0:
            monologue = monologues[0]
        else:
            monologue = "NULL"
        return monologue.strip()

    # 提取相冊圖片地址
    def get_pic_urls(self, response):
        pic_urls = []
        data_url_full = response.xpath('//li[@class=\'smallPhoto\']/@data_url_full').extract()
        if len(data_url_full) <= 1:
            pic_urls.append("");
        else:
            for pic_url in data_url_full:
                pic_urls.append(pic_url)
        if len(pic_urls) <= 1:
            return "NULL"
        # 每一個url用|分隔
        return '|'.join(pic_urls)

    # 提取年齡
    def get_age(self, response):
        age_urls = response.xpath("//dl[@class=\'personal_cen\']/dd/p[@class=\'local\']/text()").extract()
        if len(age_urls) > 0:
            age = age_urls[0]
        else:
            age = "0"
        age_words = re.split(' ', age)
        if len(age_words) <= 2:
            return "0"
        age = age_words[2][:-1]
        # 從age字符串開始匹配數字,失敗返回None
        if re.compile(r'[0-9]').match(age):
            return age
        return "0"

運行程序:

  1. Master端打開 Redis: redis-server
  2. Slave端直接運行爬蟲: scrapy crawl youyuan
  3. 多個Slave端運行爬蟲順序沒有限制。

將項目修改爲 RedisCrawlSpider 類的分佈式爬蟲,並嘗試在多個Slave端運行。

有緣網分佈式爬蟲案例:

修改 spiders/youyuan.py

在spiders目錄下增長youyuan.py文件編寫咱們的爬蟲,使其具備分佈式:

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

from scrapy.linkextractors import LinkExtractor
#from scrapy.spiders import CrawlSpider, Rule

# 1. 導入RedisCrawlSpider類,不使用CrawlSpider
from scrapy_redis.spiders import RedisCrawlSpider
from scrapy.spiders import Rule


from scrapy.dupefilters import RFPDupeFilter
from example.items import youyuanItem
import re

# 2. 修改父類 RedisCrawlSpider
# class YouyuanSpider(CrawlSpider):
class YouyuanSpider(RedisCrawlSpider):
    name = 'youyuan'

# 3. 取消 allowed_domains() 和 start_urls
##### allowed_domains = ['youyuan.com']
##### start_urls = ['http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p1/']

# 4. 增長redis-key
    redis_key = 'youyuan:start_urls'

    list_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/find/.+'))
    page_lx = LinkExtractor(allow =(r'http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p\d+/'))
    profile_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/\d+-profile/'))

    rules = (
        Rule(list_page_lx, follow=True),
        Rule(page_lx, follow=True),
        Rule(profile_page_lx, callback='parse_profile_page', follow=False),
    )

# 5. 增長__init__()方法,動態獲取allowed_domains()
    def __init__(self, *args, **kwargs):
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(youyuanSpider, self).__init__(*args, **kwargs)

    # 處理我的主頁信息,獲得咱們要的數據
    def parse_profile_page(self, response):
        item = youyuanItem()
        item['header_url'] = self.get_header_url(response)
        item['username'] = self.get_username(response)
        item['monologue'] = self.get_monologue(response)
        item['pic_urls'] = self.get_pic_urls(response)
        item['age'] = self.get_age(response)
        item['source'] = 'youyuan'
        item['source_url'] = response.url

        yield item

    # 提取頭像地址
    def get_header_url(self, response):
        header = response.xpath('//dl[@class=\'personal_cen\']/dt/img/@src').extract()
        if len(header) > 0:
            header_url = header[0]
        else:
            header_url = ""
        return header_url.strip()

    # 提取用戶名
    def get_username(self, response):
        usernames = response.xpath("//dl[@class=\'personal_cen\']/dd/div/strong/text()").extract()
        if len(usernames) > 0:
            username = usernames[0]
        else:
            username = "NULL"
        return username.strip()

    # 提取心裏獨白
    def get_monologue(self, response):
        monologues = response.xpath("//ul[@class=\'requre\']/li/p/text()").extract()
        if len(monologues) > 0:
            monologue = monologues[0]
        else:
            monologue = "NULL"
        return monologue.strip()

    # 提取相冊圖片地址
    def get_pic_urls(self, response):
        pic_urls = []
        data_url_full = response.xpath('//li[@class=\'smallPhoto\']/@data_url_full').extract()
        if len(data_url_full) <= 1:
            pic_urls.append("");
        else:
            for pic_url in data_url_full:
                pic_urls.append(pic_url)
        if len(pic_urls) <= 1:
            return "NULL"
        return '|'.join(pic_urls)

    # 提取年齡
    def get_age(self, response):
        age_urls = response.xpath("//dl[@class=\'personal_cen\']/dd/p[@class=\'local\']/text()").extract()
        if len(age_urls) > 0:
            age = age_urls[0]
        else:
            age = "0"
        age_words = re.split(' ', age)
        if len(age_words) <= 2:
            return "0"
        age = age_words[2][:-1]
        if re.compile(r'[0-9]').match(age):
            return age
        return "0" 

分佈式爬蟲執行方式:

6. 在Master端啓動redis-server:
redis-server
7. 在Slave端分別啓動爬蟲,不分前後:
scrapy runspider youyuan.py
8. 在Master端的redis-cli裏push一個start_urls
redis-cli> lpush youyuan:start_urls http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p1/
9. 爬蟲啓動,查看redis數據庫數據。

處理Redis裏的數據

有緣網的數據爬回來了,可是放在Redis裏沒有處理。以前咱們配置文件裏面沒有定製本身的ITEM_PIPELINES,而是使用了RedisPipeline,因此如今這些數據都被保存在redis的youyuan:items鍵中,因此咱們須要另外作處理。

在scrapy-youyuan目錄下能夠看到一個process_items.py文件,這個文件就是scrapy-redis的example提供的從redis讀取item進行處理的模版。

假設咱們要把youyuan:items中保存的數據讀出來寫進MongoDB或者MySQL,那麼咱們能夠本身寫一個process_youyuan_profile.py文件,而後保持後臺運行就能夠不停地將爬回來的數據入庫了。

存入MongoDB

  1. 啓動MongoDB數據庫:sudo mongod

  2. 執行下面程序:py2 process_youyuan_mongodb.py

# process_youyuan_mongodb.py

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

import json
import redis
import pymongo

def main():

    # 指定Redis數據庫信息
    rediscli = redis.StrictRedis(host='192.168.199.108', port=6379, db=0)
    # 指定MongoDB數據庫信息
    mongocli = pymongo.MongoClient(host='localhost', port=27017)

    # 建立數據庫名
    db = mongocli['youyuan']
    # 建立表名
    sheet = db['beijing_18_25']

    while True:
        # FIFO模式爲 blpop,LIFO模式爲 brpop,獲取鍵值
        source, data = rediscli.blpop(["youyuan:items"])

        item = json.loads(data)
        sheet.insert(item)

        try:
            print u"Processing: %(name)s <%(link)s>" % item
        except KeyError:
            print u"Error procesing: %r" % item

if __name__ == '__main__':
    main()

存入 MySQL

  1. 啓動mysql:mysql.server start(跟平臺不同)
  2. 登陸到root用戶:mysql -uroot -p
  3. 建立數據庫youyuan:create database youyuan;
  4. 切換到指定數據庫:use youyuan
  5. 建立表beijing_18_25以及全部字段的列名和數據類型。

  1. 執行下面程序:py2 process_youyuan_mysql.py
#process_youyuan_mysql.py

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

import json
import redis
import MySQLdb

def main():
    # 指定redis數據庫信息
    rediscli = redis.StrictRedis(host='192.168.199.108', port = 6379, db = 0)
    # 指定mysql數據庫
    mysqlcli = MySQLdb.connect(host='127.0.0.1', user='power', passwd='xxxxxxx', db = 'youyuan', port=3306, use_unicode=True)

    while True:
        # FIFO模式爲 blpop,LIFO模式爲 brpop,獲取鍵值
        source, data = rediscli.blpop(["youyuan:items"])
        item = json.loads(data)

        try:
            # 使用cursor()方法獲取操做遊標
            cur = mysqlcli.cursor()
            # 使用execute方法執行SQL INSERT語句
            cur.execute("INSERT INTO beijing_18_25 (username, crawled, age, spider, header_url, source, pic_urls, monologue, source_url) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s )", [item['username'], item['crawled'], item['age'], item['spider'], item['header_url'], item['source'], item['pic_urls'], item['monologue'], item['source_url']])
            # 提交sql事務
            mysqlcli.commit()
            #關閉本次操做
            cur.close()
            print "inserted %s" % item['source_url']
        except MySQLdb.Error,e:
            print "Mysql Error %d: %s" % (e.args[0], e.args[1])

if __name__ == '__main__':
    main() 

新浪網分類資訊爬蟲

思考:如何將已有的Scrapy爬蟲項目,改寫成scrapy-redis分佈式爬蟲。

要求:將全部對應的大類的 標題和urls、小類的 標題和urls、子連接url、文章名以及文章內容,存入Redis數據庫。

如下爲原Scrapy爬蟲項目源碼:

items.py

# -*- coding: utf-8 -*-
import scrapy
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

class SinaItem(scrapy.Item):
    # 大類的標題 和 url
    parentTitle = scrapy.Field()
    parentUrls = scrapy.Field()

    # 小類的標題 和 子url
    subTitle = scrapy.Field()
    subUrls = scrapy.Field()

    # 小類目錄存儲路徑
    subFilename = scrapy.Field()

    # 小類下的子連接
    sonUrls = scrapy.Field()

    # 文章標題和內容
    head = scrapy.Field()
    content = scrapy.Field()

pipelines.py

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

from scrapy import signals
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

class SinaPipeline(object):
    def process_item(self, item, spider):
        sonUrls = item['sonUrls']

        # 文件名爲子連接url中間部分,並將 / 替換爲 _,保存爲 .txt格式
        filename = sonUrls[7:-6].replace('/','_')
        filename += ".txt"

        fp = open(item['subFilename']+'/'+filename, 'w')
        fp.write(item['content'])
        fp.close()

        return item

settings.py

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

BOT_NAME = 'Sina'

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

ITEM_PIPELINES = {
    'Sina.pipelines.SinaPipeline': 300,
}

LOG_LEVEL = 'DEBUG'

spiders/sina.py

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

from Sina.items import SinaItem
import scrapy
import os

import sys
reload(sys)
sys.setdefaultencoding("utf-8")


class SinaSpider(scrapy.Spider):
    name= "sina"
    allowed_domains= ["sina.com.cn"]
    start_urls= [
       "http://news.sina.com.cn/guide/"
    ]

    def parse(self, response):
        items= []
        # 全部大類的url 和 標題
        parentUrls = response.xpath('//div[@id=\"tab01\"]/div/h3/a/@href').extract()
        parentTitle = response.xpath("//div[@id=\"tab01\"]/div/h3/a/text()").extract()

        # 全部小類的ur 和 標題
        subUrls  = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/@href').extract()
        subTitle = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/text()').extract()

        #爬取全部大類
        for i in range(0, len(parentTitle)):
            # 指定大類目錄的路徑和目錄名
            parentFilename = "./Data/" + parentTitle[i]

            #若是目錄不存在,則建立目錄
            if(not os.path.exists(parentFilename)):
                os.makedirs(parentFilename)

            # 爬取全部小類
            for j in range(0, len(subUrls)):
                item = SinaItem()

                # 保存大類的title和urls
                item['parentTitle'] = parentTitle[i]
                item['parentUrls'] = parentUrls[i]

                # 檢查小類的url是否以同類別大類url開頭,若是是返回True (sports.sina.com.cn 和 sports.sina.com.cn/nba)
                if_belong = subUrls[j].startswith(item['parentUrls'])

                # 若是屬於本大類,將存儲目錄放在本大類目錄下
                if(if_belong):
                    subFilename =parentFilename + '/'+ subTitle[j]
                    # 若是目錄不存在,則建立目錄
                    if(not os.path.exists(subFilename)):
                        os.makedirs(subFilename)

                    # 存儲 小類url、title和filename字段數據
                    item['subUrls'] = subUrls[j]
                    item['subTitle'] =subTitle[j]
                    item['subFilename'] = subFilename

                    items.append(item)

        #發送每一個小類url的Request請求,獲得Response連同包含meta數據 一同交給回調函數 second_parse 方法處理
        for item in items:
            yield scrapy.Request( url = item['subUrls'], meta={'meta_1': item}, callback=self.second_parse)

    #對於返回的小類的url,再進行遞歸請求
    def second_parse(self, response):
        # 提取每次Response的meta數據
        meta_1= response.meta['meta_1']

        # 取出小類裏全部子連接
        sonUrls = response.xpath('//a/@href').extract()

        items= []
        for i in range(0, len(sonUrls)):
            # 檢查每一個連接是否以大類url開頭、以.shtml結尾,若是是返回True
            if_belong = sonUrls[i].endswith('.shtml') and sonUrls[i].startswith(meta_1['parentUrls'])

            # 若是屬於本大類,獲取字段值放在同一個item下便於傳輸
            if(if_belong):
                item = SinaItem()
                item['parentTitle'] =meta_1['parentTitle']
                item['parentUrls'] =meta_1['parentUrls']
                item['subUrls'] = meta_1['subUrls']
                item['subTitle'] = meta_1['subTitle']
                item['subFilename'] = meta_1['subFilename']
                item['sonUrls'] = sonUrls[i]
                items.append(item)

        #發送每一個小類下子連接url的Request請求,獲得Response後連同包含meta數據 一同交給回調函數 detail_parse 方法處理
        for item in items:
                yield scrapy.Request(url=item['sonUrls'], meta={'meta_2':item}, callback = self.detail_parse)

    # 數據解析方法,獲取文章標題和內容
    def detail_parse(self, response):
        item = response.meta['meta_2']
        content = ""
        head = response.xpath('//h1[@id=\"main_title\"]/text()')
        content_list = response.xpath('//div[@id=\"artibody\"]/p/text()').extract()

        # 將p標籤裏的文本內容合併到一塊兒
        for content_one in content_list:
            content += content_one

        item['head']= head
        item['content']= content

        yield item

執行:

scrapy crawl sina

將已有的新浪網分類資訊Scrapy爬蟲項目,修改成基於RedisSpider類的scrapy-redis分佈式爬蟲項目

注:items數據直接存儲在Redis數據庫中,這個功能已經由scrapy-redis自行實現。除非單獨作額外處理(好比直接存入本地數據庫等),不然不用編寫pipelines.py代碼。

items.py文件

# items.py

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

import scrapy

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

class SinaItem(scrapy.Item):
    # 大類的標題 和 url
    parentTitle = scrapy.Field()
    parentUrls = scrapy.Field()

    # 小類的標題 和 子url
    subTitle = scrapy.Field()
    subUrls = scrapy.Field()

    # 小類目錄存儲路徑
    # subFilename = scrapy.Field()

    # 小類下的子連接
    sonUrls = scrapy.Field()

    # 文章標題和內容
    head = scrapy.Field()
    content = scrapy.Field() 

settings.py文件

# settings.py

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

USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

ITEM_PIPELINES = {
#    'Sina.pipelines.SinaPipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

LOG_LEVEL = 'DEBUG'

# Introduce an artifical delay to make use of parallelism. to speed up the
# crawl.
DOWNLOAD_DELAY = 1

REDIS_HOST = "192.168.13.26"
REDIS_PORT = 6379

spiders/sina.py

# sina.py

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

from Sina.items import SinaItem
from scrapy_redis.spiders import RedisSpider
#from scrapy.spiders import Spider
import scrapy

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

#class SinaSpider(Spider):
class SinaSpider(RedisSpider):
    name= "sina"
    redis_key = "sinaspider:start_urls"
    #allowed_domains= ["sina.com.cn"]
    #start_urls= [
    #   "http://news.sina.com.cn/guide/"
    #]#起始urls列表

    def __init__(self, *args, **kwargs):
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(SinaSpider, self).__init__(*args, **kwargs)


    def parse(self, response):
        items= []

        # 全部大類的url 和 標題
        parentUrls = response.xpath('//div[@id=\"tab01\"]/div/h3/a/@href').extract()
        parentTitle = response.xpath("//div[@id=\"tab01\"]/div/h3/a/text()").extract()

        # 全部小類的ur 和 標題
        subUrls  = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/@href').extract()
        subTitle = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/text()').extract()

        #爬取全部大類
        for i in range(0, len(parentTitle)):

            # 指定大類的路徑和目錄名
            #parentFilename = "./Data/" + parentTitle[i]

            #若是目錄不存在,則建立目錄
            #if(not os.path.exists(parentFilename)):
            #    os.makedirs(parentFilename)

            # 爬取全部小類
            for j in range(0, len(subUrls)):
                item = SinaItem()

                # 保存大類的title和urls
                item['parentTitle'] = parentTitle[i]
                item['parentUrls'] = parentUrls[i]

                # 檢查小類的url是否以同類別大類url開頭,若是是返回True (sports.sina.com.cn 和 sports.sina.com.cn/nba)
                if_belong = subUrls[j].startswith(item['parentUrls'])

                # 若是屬於本大類,將存儲目錄放在本大類目錄下
                if(if_belong):
                    #subFilename =parentFilename + '/'+ subTitle[j]

                    # 若是目錄不存在,則建立目錄
                    #if(not os.path.exists(subFilename)):
                    #    os.makedirs(subFilename)

                    # 存儲 小類url、title和filename字段數據
                    item['subUrls'] = subUrls[j]
                    item['subTitle'] =subTitle[j]
                    #item['subFilename'] = subFilename

                    items.append(item)

        #發送每一個小類url的Request請求,獲得Response連同包含meta數據 一同交給回調函數 second_parse 方法處理
        for item in items:
            yield scrapy.Request( url = item['subUrls'], meta={'meta_1': item}, callback=self.second_parse)

    #對於返回的小類的url,再進行遞歸請求
    def second_parse(self, response):
        # 提取每次Response的meta數據
        meta_1= response.meta['meta_1']

        # 取出小類裏全部子連接
        sonUrls = response.xpath('//a/@href').extract()

        items= []
        for i in range(0, len(sonUrls)):
            # 檢查每一個連接是否以大類url開頭、以.shtml結尾,若是是返回True
            if_belong = sonUrls[i].endswith('.shtml') and sonUrls[i].startswith(meta_1['parentUrls'])

            # 若是屬於本大類,獲取字段值放在同一個item下便於傳輸
            if(if_belong):
                item = SinaItem()
                item['parentTitle'] =meta_1['parentTitle']
                item['parentUrls'] =meta_1['parentUrls']
                item['subUrls'] =meta_1['subUrls']
                item['subTitle'] =meta_1['subTitle']
                #item['subFilename'] = meta_1['subFilename']
                item['sonUrls'] = sonUrls[i]
                items.append(item)

        #發送每一個小類下子連接url的Request請求,獲得Response後連同包含meta數據 一同交給回調函數 detail_parse 方法處理
        for item in items:
                yield scrapy.Request(url=item['sonUrls'], meta={'meta_2':item}, callback = self.detail_parse)

    # 數據解析方法,獲取文章標題和內容
    def detail_parse(self, response):
        item = response.meta['meta_2']
        content = ""
        head = response.xpath('//h1[@id=\"main_title\"]/text()').extract()
        content_list = response.xpath('//div[@id=\"artibody\"]/p/text()').extract()

        # 將p標籤裏的文本內容合併到一塊兒
        for content_one in content_list:
            content += content_one

        item['head']= head[0] if len(head) > 0 else "NULL"

        item['content']= content

        yield item 

執行:

slave端:
scrapy runspider sina.py

Master端:
redis-cli> lpush sinaspider:start_urls http://news.sina.com.cn/guide/

IT桔子是關注IT互聯網行業的結構化的公司數據庫和商業信息服務提供商,於2013年5月21日上線。

IT桔子致力於經過信息和數據的生產、聚合、挖掘、加工、處理,幫助目標用戶和客戶節約時間和金錢、提升效率,以輔助其各種商業行爲,包括風險投資、收購、競爭情報、細分行業信息、國外公司產品信息數據服務等。

用於需自行對所發表或採集的內容負責,因所發表或採集的內容引起的一切糾紛、損失,由該內容的發表或採集者承擔所有直接或間接(連帶)法律責任,IT桔子不承擔任何法律責任。

項目採集地址:http://www.itjuzi.com/company

要求:採集頁面下全部創業公司的公司信息,包括如下但不限於:

# items.py

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

class CompanyItem(scrapy.Item):

    # 公司id (url數字部分)
    info_id = scrapy.Field()
    # 公司名稱
    company_name = scrapy.Field()
    # 公司口號
    slogan = scrapy.Field()
    # 分類
    scope = scrapy.Field()
    # 子分類
    sub_scope = scrapy.Field()

    # 所在城市
    city = scrapy.Field()
    # 所在區域
    area = scrapy.Field()
    # 公司主頁
    home_page = scrapy.Field()
    # 公司標籤
    tags = scrapy.Field()

    # 公司簡介
    company_intro = scrapy.Field()
    # 公司全稱:
    company_full_name = scrapy.Field()
    # 成立時間:
    found_time = scrapy.Field()
    # 公司規模:
    company_size = scrapy.Field()
    # 運營狀態
    company_status = scrapy.Field()

    # 投資狀況列表:包含獲投時間、融資階段、融資金額、投資公司
    tz_info = scrapy.Field()
    # 團隊信息列表:包含成員姓名、成員職稱、成員介紹
    tm_info = scrapy.Field()
    # 產品信息列表:包含產品名稱、產品類型、產品介紹
    pdt_info = scrapy.Field()

項目實現:

items.py

# items.py

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

class CompanyItem(scrapy.Item):

    # 公司id (url數字部分)
    info_id = scrapy.Field()
    # 公司名稱
    company_name = scrapy.Field()
    # 公司口號
    slogan = scrapy.Field()
    # 分類
    scope = scrapy.Field()
    # 子分類
    sub_scope = scrapy.Field()

    # 所在城市
    city = scrapy.Field()
    # 所在區域
    area = scrapy.Field()
    # 公司主頁
    home_page = scrapy.Field()
    # 公司標籤
    tags = scrapy.Field()

    # 公司簡介
    company_intro = scrapy.Field()
    # 公司全稱:
    company_full_name = scrapy.Field()
    # 成立時間:
    found_time = scrapy.Field()
    # 公司規模:
    company_size = scrapy.Field()
    # 運營狀態
    company_status = scrapy.Field()

    # 投資狀況列表:包含獲投時間、融資階段、融資金額、投資公司
    tz_info = scrapy.Field()
    # 團隊信息列表:包含成員姓名、成員職稱、成員介紹
    tm_info = scrapy.Field()
    # 產品信息列表:包含產品名稱、產品類型、產品介紹
    pdt_info = scrapy.Field()

settings.py

 
# -*- coding: utf-8 -*- BOT_NAME = 'itjuzi' SPIDER_MODULES = ['itjuzi.spiders'] NEWSPIDER_MODULE = 'itjuzi.spiders' # Enables scheduling storing requests queue in redis. SCHEDULER = "scrapy_redis.scheduler.Scheduler" # Ensure all spiders share same duplicates filter through redis. DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # REDIS_START_URLS_AS_SET = True COOKIES_ENABLED = False DOWNLOAD_DELAY = 1.5 # 支持隨機下載延遲 RANDOMIZE_DOWNLOAD_DELAY = True # Obey robots.txt rules ROBOTSTXT_OBEY = False ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 } DOWNLOADER_MIDDLEWARES = { # 該中間件將會收集失敗的頁面,並在爬蟲完成後從新調度。(失敗狀況可能因爲臨時的問題,例如鏈接超時或者HTTP 500錯誤致使失敗的頁面) 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 80, # 該中間件提供了對request設置HTTP代理的支持。您能夠經過在 Request 對象中設置 proxy 元數據來開啓代理。 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 100, 'itjuzi.middlewares.RotateUserAgentMiddleware': 200, } REDIS_HOST = "192.168.199.108" REDIS_PORT = 6379

middlewares.py
# -*- coding: utf-8 -*- from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware import random # User-Agetn 下載中間件 class RotateUserAgentMiddleware(UserAgentMiddleware): def __init__(self, user_agent=''): self.user_agent = user_agent def process_request(self, request, spider): # 這句話用於隨機選擇user-agent ua = random.choice(self.user_agent_list) request.headers.setdefault('User-Agent', ua) user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10", "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/533.17.8 (KHTML, like Gecko) Version/5.0.1 Safari/533.17.8", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.1.17) Gecko/20110123 (like Firefox/3.x) SeaMonkey/2.0.12", "Mozilla/5.0 (Windows NT 5.2; rv:10.0.1) Gecko/20100101 Firefox/10.0.1 SeaMonkey/2.7.1", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.8 (KHTML, like Gecko) Chrome/4.0.302.2 Safari/532.8", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.464.0 Safari/534.3", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.54 Safari/535.2", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", "Mozilla/5.0 (Macintosh; U; Mac OS X Mach-O; en-US; rv:2.0a) Gecko/20040614 Firefox/3.0.0 ", "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.0.3) Gecko/2008092414 Firefox/3.0.3", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.14) Gecko/20110218 AlexaToolbar/alxf-2.0 Firefox/3.6.14", "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1" ]

spiders/juzi.py

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

from bs4 import BeautifulSoup
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

from scrapy_redis.spiders import RedisCrawlSpider
from itjuzi.items import CompanyItem


class ITjuziSpider(RedisCrawlSpider):
    name = 'itjuzi'
    allowed_domains = ['www.itjuzi.com']
    # start_urls = ['http://www.itjuzi.com/company']
    redis_key = 'itjuzispider:start_urls'
    rules = [
        # 獲取每一頁的連接
        Rule(link_extractor=LinkExtractor(allow=('/company\?page=\d+'))),
        # 獲取每個公司的詳情
        Rule(link_extractor=LinkExtractor(allow=('/company/\d+')), callback='parse_item')
    ]

    def parse_item(self, response):
        soup = BeautifulSoup(response.body, 'lxml')

        # 開頭部分: //div[@class="infoheadrow-v2 ugc-block-item"]
        cpy1 = soup.find('div', class_='infoheadrow-v2')
        if cpy1:
            # 公司名稱://span[@class="title"]/b/text()[1]
            company_name = cpy1.find(class_='title').b.contents[0].strip().replace('\t', '').replace('\n', '')

            # 口號: //div[@class="info-line"]/p
            slogan = cpy1.find(class_='info-line').p.get_text()

            # 分類:子分類//span[@class="scope c-gray-aset"]/a[1]
            scope_a = cpy1.find(class_='scope c-gray-aset').find_all('a')
            # 分類://span[@class="scope c-gray-aset"]/a[1]
            scope = scope_a[0].get_text().strip() if len(scope_a) > 0 else ''
            # 子分類:# //span[@class="scope c-gray-aset"]/a[2]
            sub_scope = scope_a[1].get_text().strip() if len(scope_a) > 1 else ''

            # 城市+區域://span[@class="loca c-gray-aset"]/a
            city_a = cpy1.find(class_='loca c-gray-aset').find_all('a')
            # 城市://span[@class="loca c-gray-aset"]/a[1]
            city = city_a[0].get_text().strip() if len(city_a) > 0 else ''
            # 區域://span[@class="loca c-gray-aset"]/a[2]
            area = city_a[1].get_text().strip() if len(city_a) > 1 else ''

            # 主頁://a[@class="weblink marl10"]/@href
            home_page = cpy1.find(class_='weblink marl10')['href']
            # 標籤://div[@class="tagset dbi c-gray-aset"]/a
            tags = cpy1.find(class_='tagset dbi c-gray-aset').get_text().strip().strip().replace('\n', ',')

        #基本信息://div[@class="block-inc-info on-edit-hide"]
        cpy2 = soup.find('div', class_='block-inc-info on-edit-hide')
        if cpy2:

            # 公司簡介://div[@class="block-inc-info on-edit-hide"]//div[@class="des"]
            company_intro = cpy2.find(class_='des').get_text().strip()

            # 公司全稱:成立時間:公司規模:運行狀態://div[@class="des-more"]
            cpy2_content = cpy2.find(class_='des-more').contents

            # 公司全稱://div[@class="des-more"]/div[1]
            company_full_name = cpy2_content[1].get_text().strip()[len('公司全稱:'):] if cpy2_content[1] else ''

            # 成立時間://div[@class="des-more"]/div[2]/span[1]
            found_time = cpy2_content[3].contents[1].get_text().strip()[len('成立時間:'):] if cpy2_content[3] else ''

            # 公司規模://div[@class="des-more"]/div[2]/span[2]
            company_size = cpy2_content[3].contents[3].get_text().strip()[len('公司規模:'):] if cpy2_content[3] else ''

            #運營狀態://div[@class="des-more"]/div[3]
            company_status = cpy2_content[5].get_text().strip() if cpy2_content[5] else ''

        # 主體信息:
        main = soup.find('div', class_='main')

        # 投資狀況://table[@class="list-round-v2 need2login"]
          # 投資狀況,包含獲投時間、融資階段、融資金額、投資公司
        tz = main.find('table', 'list-round-v2')
        tz_list = []
        if tz:
            all_tr = tz.find_all('tr')
            for tr in all_tr:
                tz_dict = {}
                all_td = tr.find_all('td')
                tz_dict['tz_time'] = all_td[0].span.get_text().strip()
                tz_dict['tz_round'] = all_td[1].get_text().strip()
                tz_dict['tz_finades'] = all_td[2].get_text().strip()
                tz_dict['tz_capital'] = all_td[3].get_text().strip().replace('\n', ',')
                tz_list.append(tz_dict)

        # 團隊信息:成員姓名、成員職稱、成員介紹
        tm = main.find('ul', class_='list-prodcase limited-itemnum')
        tm_list = []
        if tm:
            for li in tm.find_all('li'):
                tm_dict = {}
                tm_dict['tm_m_name'] = li.find('span', class_='c').get_text().strip()
                tm_dict['tm_m_title'] = li.find('span', class_='c-gray').get_text().strip()
                tm_dict['tm_m_intro'] = li.find('p', class_='mart10 person-des').get_text().strip()
                tm_list.append(tm_dict)

        # 產品信息:產品名稱、產品類型、產品介紹
        pdt = main.find('ul', class_='list-prod limited-itemnum')
        pdt_list = []
        if pdt:
            for li in pdt.find_all('li'):
                pdt_dict = {}
                pdt_dict['pdt_name'] = li.find('h4').b.get_text().strip()
                pdt_dict['pdt_type'] = li.find('span', class_='tag yellow').get_text().strip()
                pdt_dict['pdt_intro'] = li.find(class_='on-edit-hide').p.get_text().strip()
                pdt_list.append(pdt_dict)

        item = CompanyItem()
        item['info_id'] = response.url.split('/')[-1:][0]
        item['company_name'] = company_name
        item['slogan'] = slogan
        item['scope'] = scope
        item['sub_scope'] = sub_scope
        item['city'] = city
        item['area'] = area
        item['home_page'] = home_page
        item['tags'] = tags
        item['company_intro'] = company_intro
        item['company_full_name'] = company_full_name
        item['found_time'] = found_time
        item['company_size'] = company_size
        item['company_status'] = company_status
        item['tz_info'] = tz_list
        item['tm_info'] = tm_list
        item['pdt_info'] = pdt_list
        return item

scrapy.cfg

# Automatically created by: scrapy startproject
#
# For more information about the [deploy] section see:
# https://scrapyd.readthedocs.org/en/latest/deploy.html

[settings]
default = itjuzi.settings

[deploy]
#url = http://localhost:6800/
project = itjuzi 

運行:

Slave端:
scrapy runspider juzi.py

Master端:
redis-cli > lpush itjuzispider:start_urls http://www.itjuzi.com/company

演示效果:

相關文章
相關標籤/搜索