从零搭建Redis-Scrapy分布式爬虫

Scrapy-Redis分布式策略:

假设有四台电脑:Windows 10、Mac OS X、Ubuntu 16.04、CentOS 7.2,任意一台电脑都可以作为 Master端 或 Slaver端,比如:

  • Master端(核心服务器) :使用 Windows 10,搭建一个Redis数据库,不负责爬取,只负责url指纹判重、Request的分配,以及数据的存储

  • Slaver端(爬虫程序执行端) :使用 Mac OS X 、Ubuntu 16.04、CentOS 7.2,负责执行爬虫程序,运行过程中提交新的Request给Master

  1. 首先Slaver端从Master端拿任务(Request、url)进行数据抓取,Slaver抓取数据的同时,产生新任务的Request便提交给 Master 处理;

  2. Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,将处理后的Request加入待爬队列,并且存储爬取的数据。

Scrapy-Redis默认使用的就是这种策略,我们实现起来很简单,因为任务调度等工作Scrapy-Redis都已经帮我们做好了,我们只需要继承RedisSpider、指定redis_key就行了。

缺点是,Scrapy-Redis调度的任务是Request对象,里面信息量比较大(不仅包含url,还有callback函数、headers等信息),可能导致的结果就是会降低爬虫速度、而且会占用Redis大量的存储空间,所以如果要保证效率,那么就需要一定硬件水平。

一、安装Redis

安装Redis:http://redis.io/download

安装完成后,拷贝一份Redis安装目录下的redis.conf到任意目录,建议保存到:/etc/redis/redis.conf(Windows系统可以无需变动)

二、修改配置文件 redis.conf

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

  • 非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则默认后台运行,不必重新启动新的终端窗口执行其他命令,看个人喜好和实际需要。

三、测试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 数据库,则表示能够连接成功,可以实施分布式。

四、Redis数据库桌面管理工具

这里推荐 Redis Desktop Manager,支持 Windows、Mac OS X、Linux 等平台:

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

源码自带项目说明:

使用scrapy-redis的example来修改

先从github上拿到scrapy-redis的示例,然后将里面的example-project目录移到指定的地址:

  1. # clone github scrapy-redis源码文件
  2. git clone https://github.com/rolando/scrapy-redis.git
  3.  
  4. # 直接拿官方的项目范例,改名为自己的项目用(针对懒癌患者)
  5. mv scrapy-redis/example-project ~/scrapyredis-project

我们clone到的 scrapy-redis 源码中有自带一个example-project项目,这个项目包含3个spider,分别是dmoz, myspider_redis,mycrawler_redis。

一、dmoz (class DmozSpider(CrawlSpider))

这个爬虫继承的是CrawlSpider,它是用来说明Redis的持续性,当我们第一次运行dmoz爬虫,然后Ctrl + C停掉之后,再运行dmoz爬虫,之前的爬取记录是保留在Redis里的。

分析起来,其实这就是一个 scrapy-redis 版 CrawlSpider 类,需要设置Rule规则,以及callback不能写parse()方法。

执行方式:scrapy crawl dmoz

  1. from scrapy.linkextractors import LinkExtractor
  2. from scrapy.spiders import CrawlSpider, Rule
  3.  
  4. class DmozSpider(CrawlSpider):
  5. """Follow categories and extract links."""
  6. name = 'dmoz'
  7. allowed_domains = ['dmoz.org']
  8. start_urls = ['http://www.dmoz.org/']
  9.  
  10. rules = [
  11. Rule(LinkExtractor(
  12. restrict_css=('.top-cat', '.sub-cat', '.cat-item')
  13. ), callback='parse_directory', follow=True),
  14. ]
  15.  
  16. def parse_directory(self, response):
  17. for div in response.css('.title-and-desc'):
  18. yield {
  19. 'name': div.css('.site-title::text').extract_first(),
  20. 'description': div.css('.site-descr::text').extract_first().strip(),
  21. 'link': div.css('a::attr(href)').extract_first(),
  22. } 

二、myspider_redis (class MySpider(RedisSpider))

这个爬虫继承了RedisSpider, 它能够支持分布式的抓取,采用的是basic spider,需要写parse函数。

其次就是不再有start_urls了,取而代之的是redis_key,scrapy-redis将key从Redis里pop出来,成为请求的url地址。

  1. from scrapy_redis.spiders import RedisSpider
  2.  
  3. class MySpider(RedisSpider):
  4. """Spider that reads urls from redis queue (myspider:start_urls)."""
  5. name = 'myspider_redis'
  6.  
  7. # 注意redis-key的格式:
  8. redis_key = 'myspider:start_urls'
  9.  
  10. # 可选:等效于allowd_domains(),__init__方法按规定格式写,使用时只需要修改super()里的类名参数即可
  11. def __init__(self, *args, **kwargs):
  12. # Dynamically define the allowed domains list.
  13. domain = kwargs.pop('domain', '')
  14. self.allowed_domains = filter(None, domain.split(','))
  15.  
  16. # 修改这里的类名为当前类名
  17. super(MySpider, self).__init__(*args, **kwargs)
  18.  
  19. def parse(self, response):
  20. return {
  21. 'name': response.css('title::text').extract_first(),
  22. 'url': response.url,
  23. }

注意:

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端爬虫获取到请求,开始爬取。

三、mycrawler_redis (class MyCrawler(RedisCrawlSpider))

这个RedisCrawlSpider类爬虫继承了RedisCrawlSpider,能够支持分布式的抓取。因为采用的是crawlSpider,所以需要遵守Rule规则,以及callback不能写parse()方法。

同样也不再有start_urls了,取而代之的是redis_key,scrapy-redis将key从Redis里pop出来,成为请求的url地址。

  1. from scrapy.spiders import Rule
  2. from scrapy.linkextractors import LinkExtractor
  3.  
  4. from scrapy_redis.spiders import RedisCrawlSpider
  5.  
  6. class MyCrawler(RedisCrawlSpider):
  7. """Spider that reads urls from redis queue (myspider:start_urls)."""
  8. name = 'mycrawler_redis'
  9. redis_key = 'mycrawler:start_urls'
  10.  
  11. rules = (
  12. # follow all links
  13. Rule(LinkExtractor(), callback='parse_page', follow=True),
  14. )
  15.  
  16. # __init__方法必须按规定写,使用时只需要修改super()里的类名参数即可
  17. def __init__(self, *args, **kwargs):
  18. # Dynamically define the allowed domains list.
  19. domain = kwargs.pop('domain', '')
  20. self.allowed_domains = filter(None, domain.split(','))
  21.  
  22. # 修改这里的类名为当前类名
  23. super(MyCrawler, self).__init__(*args, **kwargs)
  24.  
  25. def parse_page(self, response):
  26. return {
  27. 'name': response.css('title::text').extract_first(),
  28. 'url': response.url,
  29. } 

注意:

同样的,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. 通常情况下,会选择用第三种方式编写深度聚焦爬虫。

有缘网分布式爬虫案例:

  1. # clone github scrapy-redis源码文件
  2. git clone https://github.com/rolando/scrapy-redis.git
  3.  
  4. # 直接拿官方的项目范例,改名为自己的项目用(针对懒癌患者)
  5. mv scrapy-redis/example-project ~/scrapy-youyuan 

修改settings.py

下面列举了修改后的配置文件中与scrapy-redis有关的部分,middleware、proxy等内容在此就省略了。

  1. # -*- coding: utf-8 -*-
  2.  
  3. # 指定使用scrapy-redis的调度器
  4. SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  5.  
  6. # 指定使用scrapy-redis的去重
  7. DUPEFILTER_CLASS = 'scrapy_redis.dupefilters.RFPDupeFilter'
  8.  
  9. # 指定排序爬取地址时使用的队列,
  10. # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
  11. SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
  12. # 可选的 按先进先出排序(FIFO)
  13. # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderQueue'
  14. # 可选的 按后进先出排序(LIFO)
  15. # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderStack'
  16.  
  17. # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
  18. SCHEDULER_PERSIST = True
  19.  
  20. # 只在使用SpiderQueue或者SpiderStack是有效的参数,指定爬虫关闭的最大间隔时间
  21. # SCHEDULER_IDLE_BEFORE_CLOSE = 10
  22.  
  23. # 通过配置RedisPipeline将item写入key为 spider.name : items 的redis的list中,供后面的分布式处理item
  24. # 这个已经由 scrapy-redis 实现,不需要我们写代码
  25. ITEM_PIPELINES = {
  26. 'example.pipelines.ExamplePipeline': 300,
  27. 'scrapy_redis.pipelines.RedisPipeline': 400
  28. }
  29.  
  30. # 指定redis数据库的连接参数
  31. # REDIS_PASS是我自己加上的redis连接密码(默认不做)
  32. REDIS_HOST = '127.0.0.1'
  33. REDIS_PORT = 6379
  34. #REDIS_PASS = 'redisP@ssw0rd'
  35.  
  36. # LOG等级
  37. LOG_LEVEL = 'DEBUG'
  38.  
  39. #默认情况下,RFPDupeFilter只记录第一个重复请求。将DUPEFILTER_DEBUG设置为True会记录所有重复的请求。
  40. DUPEFILTER_DEBUG =True
  41.  
  42. # 覆盖默认请求头,可以自己编写Downloader Middlewares设置代理和UserAgent
  43. DEFAULT_REQUEST_HEADERS = {
  44. 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  45. 'Accept-Language': 'zh-CN,zh;q=0.8',
  46. 'Connection': 'keep-alive',
  47. 'Accept-Encoding': 'gzip, deflate, sdch'
  48. }

查看pipeline.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. from datetime import datetime
  4.  
  5. class ExamplePipeline(object):
  6. def process_item(self, item, spider):
  7. #utcnow() 是获取UTC时间
  8. item["crawled"] = datetime.utcnow()
  9. # 爬虫名
  10. item["spider"] = spider.name
  11. return item

修改items.py

增加我们最后要保存的youyuanItem项,这里只写出来一个非常简单的版本

  1. # -*- coding: utf-8 -*-
  2.  
  3. from scrapy.item import Item, Field
  4.  
  5. class youyuanItem(Item):
  6. # 个人头像链接
  7. header_url = Field()
  8. # 用户名
  9. username = Field()
  10. # 内心独白
  11. monologue = Field()
  12. # 相册图片链接
  13. pic_urls = Field()
  14. # 年龄
  15. age = Field()
  16.  
  17. # 网站来源 youyuan
  18. source = Field()
  19. # 个人主页源url
  20. source_url = Field()
  21.  
  22. # 获取UTC时间
  23. crawled = Field()
  24. # 爬虫名
  25. spider = Field()

编写 spiders/youyuan.py

在spiders目录下增加youyuan.py文件编写我们的爬虫,之后就可以运行爬虫了。 这里的提供一个简单的版本:

  1. # -*- coding:utf-8 -*-
  2.  
  3. from scrapy.linkextractors import LinkExtractor
  4. from scrapy.spiders import CrawlSpider, Rule
  5. # 使用redis去重
  6. from scrapy.dupefilters import RFPDupeFilter
  7.  
  8. from example.items import youyuanItem
  9. import re
  10.  
  11. #
  12. class YouyuanSpider(CrawlSpider):
  13. name = 'youyuan'
  14. allowed_domains = ['youyuan.com']
  15. # 有缘网的列表页
  16. start_urls = ['http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p1/']
  17.  
  18. # 搜索页面匹配规则,根据response提取链接
  19. list_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/find/.+'))
  20.  
  21. # 北京、18~25岁、女性 的 搜索页面匹配规则,根据response提取链接
  22. page_lx = LinkExtractor(allow =(r'http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p\d+/'))
  23.  
  24. # 个人主页 匹配规则,根据response提取链接
  25. profile_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/\d+-profile/'))
  26.  
  27. rules = (
  28. # 匹配find页面,跟进链接,跳板
  29. Rule(list_page_lx, follow=True),
  30.  
  31. # 匹配列表页成功,跟进链接,跳板
  32. Rule(page_lx, follow=True),
  33.  
  34. # 匹配个人主页的链接,形成request保存到redis中等待调度,一旦有响应则调用parse_profile_page()回调函数处理,不做继续跟进
  35. Rule(profile_page_lx, callback='parse_profile_page', follow=False),
  36. )
  37.  
  38. # 处理个人主页信息,得到我们要的数据
  39. def parse_profile_page(self, response):
  40. item = youyuanItem()
  41. item['header_url'] = self.get_header_url(response)
  42. item['username'] = self.get_username(response)
  43. item['monologue'] = self.get_monologue(response)
  44. item['pic_urls'] = self.get_pic_urls(response)
  45. item['age'] = self.get_age(response)
  46. item['source'] = 'youyuan'
  47. item['source_url'] = response.url
  48.  
  49. #print "Processed profile %s" % response.url
  50. yield item
  51.  
  52. # 提取头像地址
  53. def get_header_url(self, response):
  54. header = response.xpath('//dl[@class=\'personal_cen\']/dt/img/@src').extract()
  55. if len(header) > 0:
  56. header_url = header[0]
  57. else:
  58. header_url = ""
  59. return header_url.strip()
  60.  
  61. # 提取用户名
  62. def get_username(self, response):
  63. usernames = response.xpath("//dl[@class=\'personal_cen\']/dd/div/strong/text()").extract()
  64. if len(usernames) > 0:
  65. username = usernames[0]
  66. else:
  67. username = "NULL"
  68. return username.strip()
  69.  
  70. # 提取内心独白
  71. def get_monologue(self, response):
  72. monologues = response.xpath("//ul[@class=\'requre\']/li/p/text()").extract()
  73. if len(monologues) > 0:
  74. monologue = monologues[0]
  75. else:
  76. monologue = "NULL"
  77. return monologue.strip()
  78.  
  79. # 提取相册图片地址
  80. def get_pic_urls(self, response):
  81. pic_urls = []
  82. data_url_full = response.xpath('//li[@class=\'smallPhoto\']/@data_url_full').extract()
  83. if len(data_url_full) <= 1:
  84. pic_urls.append("");
  85. else:
  86. for pic_url in data_url_full:
  87. pic_urls.append(pic_url)
  88. if len(pic_urls) <= 1:
  89. return "NULL"
  90. # 每个url用|分隔
  91. return '|'.join(pic_urls)
  92.  
  93. # 提取年龄
  94. def get_age(self, response):
  95. age_urls = response.xpath("//dl[@class=\'personal_cen\']/dd/p[@class=\'local\']/text()").extract()
  96. if len(age_urls) > 0:
  97. age = age_urls[0]
  98. else:
  99. age = ""
  100. age_words = re.split(' ', age)
  101. if len(age_words) <= 2:
  102. return ""
  103. age = age_words[2][:-1]
  104. # 从age字符串开始匹配数字,失败返回None
  105. if re.compile(r'[0-9]').match(age):
  106. return age
  107. return ""

运行程序:

  1. Master端打开 Redis: redis-server
  2. Slave端直接运行爬虫: scrapy crawl youyuan
  3. 多个Slave端运行爬虫顺序没有限制。

将项目修改成 RedisCrawlSpider 类的分布式爬虫,并尝试在多个Slave端运行。

有缘网分布式爬虫案例:

修改 spiders/youyuan.py

在spiders目录下增加youyuan.py文件编写我们的爬虫,使其具有分布式:

  1. # -*- coding:utf-8 -*-
  2.  
  3. from scrapy.linkextractors import LinkExtractor
  4. #from scrapy.spiders import CrawlSpider, Rule
  5.  
  6. # 1. 导入RedisCrawlSpider类,不使用CrawlSpider
  7. from scrapy_redis.spiders import RedisCrawlSpider
  8. from scrapy.spiders import Rule
  9.  
  10. from scrapy.dupefilters import RFPDupeFilter
  11. from example.items import youyuanItem
  12. import re
  13.  
  14. # 2. 修改父类 RedisCrawlSpider
  15. # class YouyuanSpider(CrawlSpider):
  16. class YouyuanSpider(RedisCrawlSpider):
  17. name = 'youyuan'
  18.  
  19. # 3. 取消 allowed_domains() 和 start_urls
  20. ##### allowed_domains = ['youyuan.com']
  21. ##### start_urls = ['http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p1/']
  22.  
  23. # 4. 增加redis-key
  24. redis_key = 'youyuan:start_urls'
  25.  
  26. list_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/find/.+'))
  27. page_lx = LinkExtractor(allow =(r'http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p\d+/'))
  28. profile_page_lx = LinkExtractor(allow=(r'http://www.youyuan.com/\d+-profile/'))
  29.  
  30. rules = (
  31. Rule(list_page_lx, follow=True),
  32. Rule(page_lx, follow=True),
  33. Rule(profile_page_lx, callback='parse_profile_page', follow=False),
  34. )
  35.  
  36. # 5. 增加__init__()方法,动态获取allowed_domains()
  37. def __init__(self, *args, **kwargs):
  38. domain = kwargs.pop('domain', '')
  39. self.allowed_domains = filter(None, domain.split(','))
  40. super(youyuanSpider, self).__init__(*args, **kwargs)
  41.  
  42. # 处理个人主页信息,得到我们要的数据
  43. def parse_profile_page(self, response):
  44. item = youyuanItem()
  45. item['header_url'] = self.get_header_url(response)
  46. item['username'] = self.get_username(response)
  47. item['monologue'] = self.get_monologue(response)
  48. item['pic_urls'] = self.get_pic_urls(response)
  49. item['age'] = self.get_age(response)
  50. item['source'] = 'youyuan'
  51. item['source_url'] = response.url
  52.  
  53. yield item
  54.  
  55. # 提取头像地址
  56. def get_header_url(self, response):
  57. header = response.xpath('//dl[@class=\'personal_cen\']/dt/img/@src').extract()
  58. if len(header) > 0:
  59. header_url = header[0]
  60. else:
  61. header_url = ""
  62. return header_url.strip()
  63.  
  64. # 提取用户名
  65. def get_username(self, response):
  66. usernames = response.xpath("//dl[@class=\'personal_cen\']/dd/div/strong/text()").extract()
  67. if len(usernames) > 0:
  68. username = usernames[0]
  69. else:
  70. username = "NULL"
  71. return username.strip()
  72.  
  73. # 提取内心独白
  74. def get_monologue(self, response):
  75. monologues = response.xpath("//ul[@class=\'requre\']/li/p/text()").extract()
  76. if len(monologues) > 0:
  77. monologue = monologues[0]
  78. else:
  79. monologue = "NULL"
  80. return monologue.strip()
  81.  
  82. # 提取相册图片地址
  83. def get_pic_urls(self, response):
  84. pic_urls = []
  85. data_url_full = response.xpath('//li[@class=\'smallPhoto\']/@data_url_full').extract()
  86. if len(data_url_full) <= 1:
  87. pic_urls.append("");
  88. else:
  89. for pic_url in data_url_full:
  90. pic_urls.append(pic_url)
  91. if len(pic_urls) <= 1:
  92. return "NULL"
  93. return '|'.join(pic_urls)
  94.  
  95. # 提取年龄
  96. def get_age(self, response):
  97. age_urls = response.xpath("//dl[@class=\'personal_cen\']/dd/p[@class=\'local\']/text()").extract()
  98. if len(age_urls) > 0:
  99. age = age_urls[0]
  100. else:
  101. age = ""
  102. age_words = re.split(' ', age)
  103. if len(age_words) <= 2:
  104. return ""
  105. age = age_words[2][:-1]
  106. if re.compile(r'[0-9]').match(age):
  107. return age
  108. return "" 

分布式爬虫执行方式:

6. 在Master端启动redis-server:
  1. redis-server
7. 在Slave端分别启动爬虫,不分先后:
  1. scrapy runspider youyuan.py
8. 在Master端的redis-cli里push一个start_urls
  1. 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

  1. # process_youyuan_mongodb.py
  2.  
  3. # -*- coding: utf-8 -*-
  4.  
  5. import json
  6. import redis
  7. import pymongo
  8.  
  9. def main():
  10.  
  11. # 指定Redis数据库信息
  12. rediscli = redis.StrictRedis(host='192.168.199.108', port=6379, db=0)
  13. # 指定MongoDB数据库信息
  14. mongocli = pymongo.MongoClient(host='localhost', port=27017)
  15.  
  16. # 创建数据库名
  17. db = mongocli['youyuan']
  18. # 创建表名
  19. sheet = db['beijing_18_25']
  20.  
  21. while True:
  22. # FIFO模式为 blpop,LIFO模式为 brpop,获取键值
  23. source, data = rediscli.blpop(["youyuan:items"])
  24.  
  25. item = json.loads(data)
  26. sheet.insert(item)
  27.  
  28. try:
  29. print u"Processing: %(name)s <%(link)s>" % item
  30. except KeyError:
  31. print u"Error procesing: %r" % item
  32.  
  33. if __name__ == '__main__':
  1.     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
  1. #process_youyuan_mysql.py
  2.  
  3. # -*- coding: utf-8 -*-
  4.  
  5. import json
  6. import redis
  7. import MySQLdb
  8.  
  9. def main():
  10. # 指定redis数据库信息
  11. rediscli = redis.StrictRedis(host='192.168.199.108', port = 6379, db = 0)
  12. # 指定mysql数据库
  13. mysqlcli = MySQLdb.connect(host='127.0.0.1', user='power', passwd='xxxxxxx', db = 'youyuan', port=3306, use_unicode=True)
  14.  
  15. while True:
  16. # FIFO模式为 blpop,LIFO模式为 brpop,获取键值
  17. source, data = rediscli.blpop(["youyuan:items"])
  18. item = json.loads(data)
  19.  
  20. try:
  21. # 使用cursor()方法获取操作游标
  22. cur = mysqlcli.cursor()
  23. # 使用execute方法执行SQL INSERT语句
  24. 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']])
  25. # 提交sql事务
  26. mysqlcli.commit()
  27. #关闭本次操作
  28. cur.close()
  29. print "inserted %s" % item['source_url']
  30. except MySQLdb.Error,e:
  31. print "Mysql Error %d: %s" % (e.args[0], e.args[1])
  32.  
  33. if __name__ == '__main__':
  34. main() 

新浪网分类资讯爬虫

思考:如何将已有的Scrapy爬虫项目,改写成scrapy-redis分布式爬虫。

要求:将所有对应的大类的 标题和urls、小类的 标题和urls、子链接url、文章名以及文章内容,存入Redis数据库。

以下为原Scrapy爬虫项目源码:

items.py

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. import sys
  4. reload(sys)
  5. sys.setdefaultencoding("utf-8")
  6.  
  7. class SinaItem(scrapy.Item):
  8. # 大类的标题 和 url
  9. parentTitle = scrapy.Field()
  10. parentUrls = scrapy.Field()
  11.  
  12. # 小类的标题 和 子url
  13. subTitle = scrapy.Field()
  14. subUrls = scrapy.Field()
  15.  
  16. # 小类目录存储路径
  17. subFilename = scrapy.Field()
  18.  
  19. # 小类下的子链接
  20. sonUrls = scrapy.Field()
  21.  
  22. # 文章标题和内容
  23. head = scrapy.Field()
  24. content = scrapy.Field()

pipelines.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. from scrapy import signals
  4. import sys
  5. reload(sys)
  6. sys.setdefaultencoding("utf-8")
  7.  
  8. class SinaPipeline(object):
  9. def process_item(self, item, spider):
  10. sonUrls = item['sonUrls']
  11.  
  12. # 文件名为子链接url中间部分,并将 / 替换为 _,保存为 .txt格式
  13. filename = sonUrls[7:-6].replace('/','_')
  14. filename += ".txt"
  15.  
  16. fp = open(item['subFilename']+'/'+filename, 'w')
  17. fp.write(item['content'])
  18. fp.close()
  19.  
  20. return item

settings.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. BOT_NAME = 'Sina'
  4.  
  5. SPIDER_MODULES = ['Sina.spiders']
  6. NEWSPIDER_MODULE = 'Sina.spiders'
  7.  
  8. ITEM_PIPELINES = {
  9. 'Sina.pipelines.SinaPipeline': 300,
  10. }
  11.  
  12. LOG_LEVEL = 'DEBUG'

spiders/sina.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. from Sina.items import SinaItem
  4. import scrapy
  5. import os
  6.  
  7. import sys
  8. reload(sys)
  9. sys.setdefaultencoding("utf-8")
  10.  
  11. class SinaSpider(scrapy.Spider):
  12. name= "sina"
  13. allowed_domains= ["sina.com.cn"]
  14. start_urls= [
  15. "http://news.sina.com.cn/guide/"
  16. ]
  17.  
  18. def parse(self, response):
  19. items= []
  20. # 所有大类的url 和 标题
  21. parentUrls = response.xpath('//div[@id=\"tab01\"]/div/h3/a/@href').extract()
  22. parentTitle = response.xpath("//div[@id=\"tab01\"]/div/h3/a/text()").extract()
  23.  
  24. # 所有小类的ur 和 标题
  25. subUrls = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/@href').extract()
  26. subTitle = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/text()').extract()
  27.  
  28. #爬取所有大类
  29. for i in range(0, len(parentTitle)):
  30. # 指定大类目录的路径和目录名
  31. parentFilename = "./Data/" + parentTitle[i]
  32.  
  33. #如果目录不存在,则创建目录
  34. if(not os.path.exists(parentFilename)):
  35. os.makedirs(parentFilename)
  36.  
  37. # 爬取所有小类
  38. for j in range(0, len(subUrls)):
  39. item = SinaItem()
  40.  
  41. # 保存大类的title和urls
  42. item['parentTitle'] = parentTitle[i]
  43. item['parentUrls'] = parentUrls[i]
  44.  
  45. # 检查小类的url是否以同类别大类url开头,如果是返回True (sports.sina.com.cn 和 sports.sina.com.cn/nba)
  46. if_belong = subUrls[j].startswith(item['parentUrls'])
  47.  
  48. # 如果属于本大类,将存储目录放在本大类目录下
  49. if(if_belong):
  50. subFilename =parentFilename + '/'+ subTitle[j]
  51. # 如果目录不存在,则创建目录
  52. if(not os.path.exists(subFilename)):
  53. os.makedirs(subFilename)
  54.  
  55. # 存储 小类url、title和filename字段数据
  56. item['subUrls'] = subUrls[j]
  57. item['subTitle'] =subTitle[j]
  58. item['subFilename'] = subFilename
  59.  
  60. items.append(item)
  61.  
  62. #发送每个小类url的Request请求,得到Response连同包含meta数据 一同交给回调函数 second_parse 方法处理
  63. for item in items:
  64. yield scrapy.Request( url = item['subUrls'], meta={'meta_1': item}, callback=self.second_parse)
  65.  
  66. #对于返回的小类的url,再进行递归请求
  67. def second_parse(self, response):
  68. # 提取每次Response的meta数据
  69. meta_1= response.meta['meta_1']
  70.  
  71. # 取出小类里所有子链接
  72. sonUrls = response.xpath('//a/@href').extract()
  73.  
  74. items= []
  75. for i in range(0, len(sonUrls)):
  76. # 检查每个链接是否以大类url开头、以.shtml结尾,如果是返回True
  77. if_belong = sonUrls[i].endswith('.shtml') and sonUrls[i].startswith(meta_1['parentUrls'])
  78.  
  79. # 如果属于本大类,获取字段值放在同一个item下便于传输
  80. if(if_belong):
  81. item = SinaItem()
  82. item['parentTitle'] =meta_1['parentTitle']
  83. item['parentUrls'] =meta_1['parentUrls']
  84. item['subUrls'] = meta_1['subUrls']
  85. item['subTitle'] = meta_1['subTitle']
  86. item['subFilename'] = meta_1['subFilename']
  87. item['sonUrls'] = sonUrls[i]
  88. items.append(item)
  89.  
  90. #发送每个小类下子链接url的Request请求,得到Response后连同包含meta数据 一同交给回调函数 detail_parse 方法处理
  91. for item in items:
  92. yield scrapy.Request(url=item['sonUrls'], meta={'meta_2':item}, callback = self.detail_parse)
  93.  
  94. # 数据解析方法,获取文章标题和内容
  95. def detail_parse(self, response):
  96. item = response.meta['meta_2']
  97. content = ""
  98. head = response.xpath('//h1[@id=\"main_title\"]/text()')
  99. content_list = response.xpath('//div[@id=\"artibody\"]/p/text()').extract()
  100.  
  101. # 将p标签里的文本内容合并到一起
  102. for content_one in content_list:
  103. content += content_one
  104.  
  105. item['head']= head
  106. item['content']= content
  107.  
  108. yield item

执行:

  1. scrapy crawl sina

将已有的新浪网分类资讯Scrapy爬虫项目,修改为基于RedisSpider类的scrapy-redis分布式爬虫项目

注:items数据直接存储在Redis数据库中,这个功能已经由scrapy-redis自行实现。除非单独做额外处理(比如直接存入本地数据库等),否则不用编写pipelines.py代码。

items.py文件

  1. # items.py
  2.  
  3. # -*- coding: utf-8 -*-
  4.  
  5. import scrapy
  6.  
  7. import sys
  8. reload(sys)
  9. sys.setdefaultencoding("utf-8")
  10.  
  11. class SinaItem(scrapy.Item):
  12. # 大类的标题 和 url
  13. parentTitle = scrapy.Field()
  14. parentUrls = scrapy.Field()
  15.  
  16. # 小类的标题 和 子url
  17. subTitle = scrapy.Field()
  18. subUrls = scrapy.Field()
  19.  
  20. # 小类目录存储路径
  21. # subFilename = scrapy.Field()
  22.  
  23. # 小类下的子链接
  24. sonUrls = scrapy.Field()
  25.  
  26. # 文章标题和内容
  27. head = scrapy.Field()
  28. content = scrapy.Field() 

settings.py文件

  1. # settings.py
  2.  
  3. SPIDER_MODULES = ['Sina.spiders']
  4. NEWSPIDER_MODULE = 'Sina.spiders'
  5.  
  6. USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'
  7.  
  8. DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
  9. SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  10. SCHEDULER_PERSIST = True
  11. SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
  12. #SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
  13. #SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"
  14.  
  15. ITEM_PIPELINES = {
  16. # 'Sina.pipelines.SinaPipeline': 300,
  17. 'scrapy_redis.pipelines.RedisPipeline': 400,
  18. }
  19.  
  20. LOG_LEVEL = 'DEBUG'
  21.  
  22. # Introduce an artifical delay to make use of parallelism. to speed up the
  23. # crawl.
  24. DOWNLOAD_DELAY = 1
  25.  
  26. REDIS_HOST = "192.168.13.26"
  27. REDIS_PORT = 6379

spiders/sina.py

  1. # sina.py
  2.  
  3. # -*- coding: utf-8 -*-
  4.  
  5. from Sina.items import SinaItem
  6. from scrapy_redis.spiders import RedisSpider
  7. #from scrapy.spiders import Spider
  8. import scrapy
  9.  
  10. import sys
  11. reload(sys)
  12. sys.setdefaultencoding("utf-8")
  13.  
  14. #class SinaSpider(Spider):
  15. class SinaSpider(RedisSpider):
  16. name= "sina"
  17. redis_key = "sinaspider:start_urls"
  18. #allowed_domains= ["sina.com.cn"]
  19. #start_urls= [
  20. # "http://news.sina.com.cn/guide/"
  21. #]#起始urls列表
  22.  
  23. def __init__(self, *args, **kwargs):
  24. domain = kwargs.pop('domain', '')
  25. self.allowed_domains = filter(None, domain.split(','))
  26. super(SinaSpider, self).__init__(*args, **kwargs)
  27.  
  28. def parse(self, response):
  29. items= []
  30.  
  31. # 所有大类的url 和 标题
  32. parentUrls = response.xpath('//div[@id=\"tab01\"]/div/h3/a/@href').extract()
  33. parentTitle = response.xpath("//div[@id=\"tab01\"]/div/h3/a/text()").extract()
  34.  
  35. # 所有小类的ur 和 标题
  36. subUrls = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/@href').extract()
  37. subTitle = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/text()').extract()
  38.  
  39. #爬取所有大类
  40. for i in range(0, len(parentTitle)):
  41.  
  42. # 指定大类的路径和目录名
  43. #parentFilename = "./Data/" + parentTitle[i]
  44.  
  45. #如果目录不存在,则创建目录
  46. #if(not os.path.exists(parentFilename)):
  47. # os.makedirs(parentFilename)
  48.  
  49. # 爬取所有小类
  50. for j in range(0, len(subUrls)):
  51. item = SinaItem()
  52.  
  53. # 保存大类的title和urls
  54. item['parentTitle'] = parentTitle[i]
  55. item['parentUrls'] = parentUrls[i]
  56.  
  57. # 检查小类的url是否以同类别大类url开头,如果是返回True (sports.sina.com.cn 和 sports.sina.com.cn/nba)
  58. if_belong = subUrls[j].startswith(item['parentUrls'])
  59.  
  60. # 如果属于本大类,将存储目录放在本大类目录下
  61. if(if_belong):
  62. #subFilename =parentFilename + '/'+ subTitle[j]
  63.  
  64. # 如果目录不存在,则创建目录
  65. #if(not os.path.exists(subFilename)):
  66. # os.makedirs(subFilename)
  67.  
  68. # 存储 小类url、title和filename字段数据
  69. item['subUrls'] = subUrls[j]
  70. item['subTitle'] =subTitle[j]
  71. #item['subFilename'] = subFilename
  72.  
  73. items.append(item)
  74.  
  75. #发送每个小类url的Request请求,得到Response连同包含meta数据 一同交给回调函数 second_parse 方法处理
  76. for item in items:
  77. yield scrapy.Request( url = item['subUrls'], meta={'meta_1': item}, callback=self.second_parse)
  78.  
  79. #对于返回的小类的url,再进行递归请求
  80. def second_parse(self, response):
  81. # 提取每次Response的meta数据
  82. meta_1= response.meta['meta_1']
  83.  
  84. # 取出小类里所有子链接
  85. sonUrls = response.xpath('//a/@href').extract()
  86.  
  87. items= []
  88. for i in range(0, len(sonUrls)):
  89. # 检查每个链接是否以大类url开头、以.shtml结尾,如果是返回True
  90. if_belong = sonUrls[i].endswith('.shtml') and sonUrls[i].startswith(meta_1['parentUrls'])
  91.  
  92. # 如果属于本大类,获取字段值放在同一个item下便于传输
  93. if(if_belong):
  94. item = SinaItem()
  95. item['parentTitle'] =meta_1['parentTitle']
  96. item['parentUrls'] =meta_1['parentUrls']
  97. item['subUrls'] =meta_1['subUrls']
  98. item['subTitle'] =meta_1['subTitle']
  99. #item['subFilename'] = meta_1['subFilename']
  100. item['sonUrls'] = sonUrls[i]
  101. items.append(item)
  102.  
  103. #发送每个小类下子链接url的Request请求,得到Response后连同包含meta数据 一同交给回调函数 detail_parse 方法处理
  104. for item in items:
  105. yield scrapy.Request(url=item['sonUrls'], meta={'meta_2':item}, callback = self.detail_parse)
  106.  
  107. # 数据解析方法,获取文章标题和内容
  108. def detail_parse(self, response):
  109. item = response.meta['meta_2']
  110. content = ""
  111. head = response.xpath('//h1[@id=\"main_title\"]/text()').extract()
  112. content_list = response.xpath('//div[@id=\"artibody\"]/p/text()').extract()
  113.  
  114. # 将p标签里的文本内容合并到一起
  115. for content_one in content_list:
  116. content += content_one
  117.  
  118. item['head']= head[0] if len(head) > 0 else "NULL"
  119.  
  120. item['content']= content
  121.  
  122. yield item 

执行:

  1. slave端:
  2. scrapy runspider sina.py
  3.  
  4. Master端:
  5. redis-cli> lpush sinaspider:start_urls http://news.sina.com.cn/guide/

IT桔子是关注IT互联网行业的结构化的公司数据库和商业信息服务提供商,于2013年5月21日上线。

IT桔子致力于通过信息和数据的生产、聚合、挖掘、加工、处理,帮助目标用户和客户节约时间和金钱、提高效率,以辅助其各类商业行为,包括风险投资、收购、竞争情报、细分行业信息、国外公司产品信息数据服务等。

用于需自行对所发表或采集的内容负责,因所发表或采集的内容引发的一切纠纷、损失,由该内容的发表或采集者承担全部直接或间接(连带)法律责任,IT桔子不承担任何法律责任。

项目采集地址:http://www.itjuzi.com/company

要求:采集页面下所有创业公司的公司信息,包括以下但不限于:

  1. # items.py
  2.  
  3. # -*- coding: utf-8 -*-
  4. import scrapy
  5.  
  6. class CompanyItem(scrapy.Item):
  7.  
  8. # 公司id (url数字部分)
  9. info_id = scrapy.Field()
  10. # 公司名称
  11. company_name = scrapy.Field()
  12. # 公司口号
  13. slogan = scrapy.Field()
  14. # 分类
  15. scope = scrapy.Field()
  16. # 子分类
  17. sub_scope = scrapy.Field()
  18.  
  19. # 所在城市
  20. city = scrapy.Field()
  21. # 所在区域
  22. area = scrapy.Field()
  23. # 公司主页
  24. home_page = scrapy.Field()
  25. # 公司标签
  26. tags = scrapy.Field()
  27.  
  28. # 公司简介
  29. company_intro = scrapy.Field()
  30. # 公司全称:
  31. company_full_name = scrapy.Field()
  32. # 成立时间:
  33. found_time = scrapy.Field()
  34. # 公司规模:
  35. company_size = scrapy.Field()
  36. # 运营状态
  37. company_status = scrapy.Field()
  38.  
  39. # 投资情况列表:包含获投时间、融资阶段、融资金额、投资公司
  40. tz_info = scrapy.Field()
  41. # 团队信息列表:包含成员姓名、成员职称、成员介绍
  42. tm_info = scrapy.Field()
  43. # 产品信息列表:包含产品名称、产品类型、产品介绍
  44. pdt_info = scrapy.Field()

项目实现:

items.py

  1. # items.py
  2.  
  3. # -*- coding: utf-8 -*-
  4. import scrapy
  5.  
  6. class CompanyItem(scrapy.Item):
  7.  
  8. # 公司id (url数字部分)
  9. info_id = scrapy.Field()
  10. # 公司名称
  11. company_name = scrapy.Field()
  12. # 公司口号
  13. slogan = scrapy.Field()
  14. # 分类
  15. scope = scrapy.Field()
  16. # 子分类
  17. sub_scope = scrapy.Field()
  18.  
  19. # 所在城市
  20. city = scrapy.Field()
  21. # 所在区域
  22. area = scrapy.Field()
  23. # 公司主页
  24. home_page = scrapy.Field()
  25. # 公司标签
  26. tags = scrapy.Field()
  27.  
  28. # 公司简介
  29. company_intro = scrapy.Field()
  30. # 公司全称:
  31. company_full_name = scrapy.Field()
  32. # 成立时间:
  33. found_time = scrapy.Field()
  34. # 公司规模:
  35. company_size = scrapy.Field()
  36. # 运营状态
  37. company_status = scrapy.Field()
  38.  
  39. # 投资情况列表:包含获投时间、融资阶段、融资金额、投资公司
  40. tz_info = scrapy.Field()
  41. # 团队信息列表:包含成员姓名、成员职称、成员介绍
  42. tm_info = scrapy.Field()
  43. # 产品信息列表:包含产品名称、产品类型、产品介绍
  44. pdt_info = scrapy.Field()

settings.py

  1.  
  1. # -*- coding: utf-8 -*-
  2. BOT_NAME = 'itjuzi'
  3. SPIDER_MODULES = ['itjuzi.spiders']
  4. NEWSPIDER_MODULE = 'itjuzi.spiders'
  5. # Enables scheduling storing requests queue in redis.
  6. SCHEDULER = "scrapy_redis.scheduler.Scheduler"
  7. # Ensure all spiders share same duplicates filter through redis.
  8. DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
  9. # REDIS_START_URLS_AS_SET = True
  10. COOKIES_ENABLED = False
  11. DOWNLOAD_DELAY = 1.5
  12. # 支持随机下载延迟
  13. RANDOMIZE_DOWNLOAD_DELAY = True
  14. # Obey robots.txt rules
  15. ROBOTSTXT_OBEY = False
  16. ITEM_PIPELINES = {
  17. 'scrapy_redis.pipelines.RedisPipeline': 300
  18. }
  19. DOWNLOADER_MIDDLEWARES = {
  20. # 该中间件将会收集失败的页面,并在爬虫完成后重新调度。(失败情况可能由于临时的问题,例如连接超时或者HTTP 500错误导致失败的页面)
  21. 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 80,
  22. # 该中间件提供了对request设置HTTP代理的支持。您可以通过在 Request 对象中设置 proxy 元数据来开启代理。
  23. 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 100,
  24. 'itjuzi.middlewares.RotateUserAgentMiddleware': 200,
  25. }
  26. REDIS_HOST = "192.168.199.108"
  27. REDIS_PORT = 6379
  1. middlewares.py
  2. # -*- coding: utf-8 -*-
  3.  
  4. from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware
  5. import random
  6.  
  7. # User-Agetn 下载中间件
  8. class RotateUserAgentMiddleware(UserAgentMiddleware):
  9. def __init__(self, user_agent=''):
  10. self.user_agent = user_agent
  11.  
  12. def process_request(self, request, spider):
  13. # 这句话用于随机选择user-agent
  14. ua = random.choice(self.user_agent_list)
  15. request.headers.setdefault('User-Agent', ua)
  16.  
  17. user_agent_list = [
  18. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
  19. "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
  20. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
  21. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
  22. "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
  23. "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
  24. "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
  25. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
  26. "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
  27. "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",
  28. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
  29. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
  30. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
  31. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
  32. "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
  33. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
  34. "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
  35. "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
  36. "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",
  37. "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",
  38. "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",
  39. "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",
  40. "Mozilla/5.0 (Windows NT 5.2; rv:10.0.1) Gecko/20100101 Firefox/10.0.1 SeaMonkey/2.7.1",
  41. "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",
  42. "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",
  43. "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",
  44. "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",
  45. "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",
  46. "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",
  47. "Mozilla/5.0 (Macintosh; U; Mac OS X Mach-O; en-US; rv:2.0a) Gecko/20040614 Firefox/3.0.0 ",
  48. "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.0.3) Gecko/2008092414 Firefox/3.0.3",
  49. "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5",
  50. "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",
  51. "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15",
  52. "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
  53. ]

spiders/juzi.py

  1. # -*- coding: utf-8 -*-
  2.  
  3. from bs4 import BeautifulSoup
  4. from scrapy.linkextractors import LinkExtractor
  5. from scrapy.spiders import CrawlSpider, Rule
  6.  
  7. from scrapy_redis.spiders import RedisCrawlSpider
  8. from itjuzi.items import CompanyItem
  9.  
  10. class ITjuziSpider(RedisCrawlSpider):
  11. name = 'itjuzi'
  12. allowed_domains = ['www.itjuzi.com']
  13. # start_urls = ['http://www.itjuzi.com/company']
  14. redis_key = 'itjuzispider:start_urls'
  15. rules = [
  16. # 获取每一页的链接
  17. Rule(link_extractor=LinkExtractor(allow=('/company\?page=\d+'))),
  18. # 获取每一个公司的详情
  19. Rule(link_extractor=LinkExtractor(allow=('/company/\d+')), callback='parse_item')
  20. ]
  21.  
  22. def parse_item(self, response):
  23. soup = BeautifulSoup(response.body, 'lxml')
  24.  
  25. # 开头部分: //div[@class="infoheadrow-v2 ugc-block-item"]
  26. cpy1 = soup.find('div', class_='infoheadrow-v2')
  27. if cpy1:
  28. # 公司名称://span[@class="title"]/b/text()[1]
  29. company_name = cpy1.find(class_='title').b.contents[0].strip().replace('\t', '').replace('\n', '')
  30.  
  31. # 口号: //div[@class="info-line"]/p
  32. slogan = cpy1.find(class_='info-line').p.get_text()
  33.  
  34. # 分类:子分类//span[@class="scope c-gray-aset"]/a[1]
  35. scope_a = cpy1.find(class_='scope c-gray-aset').find_all('a')
  36. # 分类://span[@class="scope c-gray-aset"]/a[1]
  37. scope = scope_a[0].get_text().strip() if len(scope_a) > 0 else ''
  38. # 子分类:# //span[@class="scope c-gray-aset"]/a[2]
  39. sub_scope = scope_a[1].get_text().strip() if len(scope_a) > 1 else ''
  40.  
  41. # 城市+区域://span[@class="loca c-gray-aset"]/a
  42. city_a = cpy1.find(class_='loca c-gray-aset').find_all('a')
  43. # 城市://span[@class="loca c-gray-aset"]/a[1]
  44. city = city_a[0].get_text().strip() if len(city_a) > 0 else ''
  45. # 区域://span[@class="loca c-gray-aset"]/a[2]
  46. area = city_a[1].get_text().strip() if len(city_a) > 1 else ''
  47.  
  48. # 主页://a[@class="weblink marl10"]/@href
  49. home_page = cpy1.find(class_='weblink marl10')['href']
  50. # 标签://div[@class="tagset dbi c-gray-aset"]/a
  51. tags = cpy1.find(class_='tagset dbi c-gray-aset').get_text().strip().strip().replace('\n', ',')
  52.  
  53. #基本信息://div[@class="block-inc-info on-edit-hide"]
  54. cpy2 = soup.find('div', class_='block-inc-info on-edit-hide')
  55. if cpy2:
  56.  
  57. # 公司简介://div[@class="block-inc-info on-edit-hide"]//div[@class="des"]
  58. company_intro = cpy2.find(class_='des').get_text().strip()
  59.  
  60. # 公司全称:成立时间:公司规模:运行状态://div[@class="des-more"]
  61. cpy2_content = cpy2.find(class_='des-more').contents
  62.  
  63. # 公司全称://div[@class="des-more"]/div[1]
  64. company_full_name = cpy2_content[1].get_text().strip()[len('公司全称:'):] if cpy2_content[1] else ''
  65.  
  66. # 成立时间://div[@class="des-more"]/div[2]/span[1]
  67. found_time = cpy2_content[3].contents[1].get_text().strip()[len('成立时间:'):] if cpy2_content[3] else ''
  68.  
  69. # 公司规模://div[@class="des-more"]/div[2]/span[2]
  70. company_size = cpy2_content[3].contents[3].get_text().strip()[len('公司规模:'):] if cpy2_content[3] else ''
  71.  
  72. #运营状态://div[@class="des-more"]/div[3]
  73. company_status = cpy2_content[5].get_text().strip() if cpy2_content[5] else ''
  74.  
  75. # 主体信息:
  76. main = soup.find('div', class_='main')
  77.  
  78. # 投资情况://table[@class="list-round-v2 need2login"]
  79. # 投资情况,包含获投时间、融资阶段、融资金额、投资公司
  80. tz = main.find('table', 'list-round-v2')
  81. tz_list = []
  82. if tz:
  83. all_tr = tz.find_all('tr')
  84. for tr in all_tr:
  85. tz_dict = {}
  86. all_td = tr.find_all('td')
  87. tz_dict['tz_time'] = all_td[0].span.get_text().strip()
  88. tz_dict['tz_round'] = all_td[1].get_text().strip()
  89. tz_dict['tz_finades'] = all_td[2].get_text().strip()
  90. tz_dict['tz_capital'] = all_td[3].get_text().strip().replace('\n', ',')
  91. tz_list.append(tz_dict)
  92.  
  93. # 团队信息:成员姓名、成员职称、成员介绍
  94. tm = main.find('ul', class_='list-prodcase limited-itemnum')
  95. tm_list = []
  96. if tm:
  97. for li in tm.find_all('li'):
  98. tm_dict = {}
  99. tm_dict['tm_m_name'] = li.find('span', class_='c').get_text().strip()
  100. tm_dict['tm_m_title'] = li.find('span', class_='c-gray').get_text().strip()
  101. tm_dict['tm_m_intro'] = li.find('p', class_='mart10 person-des').get_text().strip()
  102. tm_list.append(tm_dict)
  103.  
  104. # 产品信息:产品名称、产品类型、产品介绍
  105. pdt = main.find('ul', class_='list-prod limited-itemnum')
  106. pdt_list = []
  107. if pdt:
  108. for li in pdt.find_all('li'):
  109. pdt_dict = {}
  110. pdt_dict['pdt_name'] = li.find('h4').b.get_text().strip()
  111. pdt_dict['pdt_type'] = li.find('span', class_='tag yellow').get_text().strip()
  112. pdt_dict['pdt_intro'] = li.find(class_='on-edit-hide').p.get_text().strip()
  113. pdt_list.append(pdt_dict)
  114.  
  115. item = CompanyItem()
  116. item['info_id'] = response.url.split('/')[-1:][0]
  117. item['company_name'] = company_name
  118. item['slogan'] = slogan
  119. item['scope'] = scope
  120. item['sub_scope'] = sub_scope
  121. item['city'] = city
  122. item['area'] = area
  123. item['home_page'] = home_page
  124. item['tags'] = tags
  125. item['company_intro'] = company_intro
  126. item['company_full_name'] = company_full_name
  127. item['found_time'] = found_time
  128. item['company_size'] = company_size
  129. item['company_status'] = company_status
  130. item['tz_info'] = tz_list
  131. item['tm_info'] = tm_list
  132. item['pdt_info'] = pdt_list
  133. return item

scrapy.cfg

  1. # Automatically created by: scrapy startproject
  2. #
  3. # For more information about the [deploy] section see:
  4. # https://scrapyd.readthedocs.org/en/latest/deploy.html
  5.  
  6. [settings]
  7. default = itjuzi.settings
  8.  
  9. [deploy]
  10. #url = http://localhost:6800/
  11. project = itjuzi 

运行:

  1. Slave端:
  2. scrapy runspider juzi.py
  3.  
  4. Master端:
  5. redis-cli > lpush itjuzispider:start_urls http://www.itjuzi.com/company

演示效果:

scrapy与redis实战的更多相关文章

  1. scrapy电影天堂实战(二)创建爬虫项目

    公众号原文 创建数据库 我在上一篇笔记中已经创建了数据库,具体查看<scrapy电影天堂实战(一)创建数据库>,这篇笔记创建scrapy实例,先熟悉下要用到到xpath知识 用到的xpat ...

  2. Redis实战阅读笔记——开始

    Redis实战这本书,看完以后最大的不是redis本身的东西,而是作者面对实际问题的分析而给出的设计方案,可以看成NoSql设计的应用.个人从这方面收获很多,至于Redis本身的东西,这个就花一两个小 ...

  3. Redis实战阅读笔记——第一章

    Redis 实战 中文版 的20-21页看的人郁闷死了,最后看英文版才明白意思,哎,我理解能力差成这样了 其中,图 1-12 有错误,草,这个是英文版的错--应该是group:programming

  4. redis实战(01)_redis安装

    早就想对redis进行实战操作了,最近看了一些视频和参考书籍,总结总结一下,redis实战内容: 实战前先对redis做一个大概的认识: 现在开始安装redis了... redis的安装下载地址 ht ...

  5. C# Redis实战(二) [转]

    二.Redis服务  在C# Redis实战(一)中我将所有文件拷贝到了D盘redis文件夹下,其中redis-server.exe即为其服务端程序,双击即开始运行,如图               ...

  6. (转)国内外三个不同领域巨头分享的Redis实战经验及使用场景

    随着应用对高性能需求的增加,NoSQL逐渐在各大名企的系统架构中生根发芽.这里我们将为大家分享社交巨头新浪微博.传媒巨头Viacom及图片分享领域佼佼者Pinterest带来的Redis实践,首先我们 ...

  7. C# Redis实战

    转自  :http://blog.csdn.net/qiujialongjjj/article/details/16945569 一.初步准备 Redis 是一个开源的使用ANSI C 语言编写.支持 ...

  8. Redis实战

    大约一年多前,公司同事开始使用Redis,不清楚是配置,还是版本的问题,当时的Redis经常在使用一段时间后,连接爆满且不释放.印象中,Redis 2.4.8以下的版本由于设计上的主从库同步问题,就会 ...

  9. Redis实战之Redis + Jedis

    用Memcached,对于缓存对象大小有要求,单个对象不得大于1MB,且不支持复杂的数据类型,譬如SET 等.基于这些限制,有必要考虑Redis! 相关链接: Redis实战 Redis实战之Redi ...

随机推荐

  1. python学习笔记(五)---sublime text 多行代码注释快捷键

    转载网址:https://blog.csdn.net/mycms5/article/details/70194045/ 多行选择后按下ctrl+/ 选择类 Ctrl+D 选中光标所占的文本,继续操作则 ...

  2. Python - learn note(1)

    1. 下载安装Python 2.7(为了向下兼容以前的版本), Python 3.5(VS2015不支持配置3.6的环境) 教程 需要使用VS2015进行开发,必须勾选上后面两项: 2. VS2015 ...

  3. poj 1789 Truck History 最小生成树 prim 难度:0

    Truck History Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 19122   Accepted: 7366 De ...

  4. java之正则表达式的使用1

    正则表达式: 主要作用: a.匹配 b.切割 c.替换 d.获取 1.反斜杠和转义字符 废话不多说,直接上demo public static void main(String[] args) { / ...

  5. 高并发数据采集的架构应用(Redis的应用)

    问题的出发点:       最近公司为了发展需要,要扩大对用户的信息采集,每个用户的采集量估计约3W.如果用户量增加的话,将会大量照成采集量成3W倍的增长,但是又要满足日常业务需要,特别是报表数据必要 ...

  6. 【转】 linux的网络接口之扫盲

    [转] linux的网络接口之扫盲 转自:http://blog.csdn.net/zhangxinrun/article/details/6820433 (1)网络接口的命名 这里并不存在一定的命名 ...

  7. LNMP架构下的nginx、mysql、php的源码安装

    一.LNMP的介绍 LNMP就是Linux+Nginx+Mysql+Php这种网站服务架构.Linux是一类Unix计算机操作系统的统称,是目前最流行的免费操作系统,常见版本有:centos.ubun ...

  8. protel99 se中出现许多Backup of 文件,修改过保存时,总会出现备份文件,怎么才能取消这一设置?

    在file选项左边有个向下的大箭头标示 点开 有个prefereces项 点开之 把create backup项勾掉即可

  9. vim自动缩进

    最近写完程序,在进行调试时发现特别困难,代码乱的一塌糊涂,特别是代码量很多时,调试起来特别囧,逻辑很难理清. 这让我想起了缩进功能,可以让代码自动对齐. gedit编辑器在菜单栏里的编辑->首选 ...

  10. C# 空合并操作符(??)不可重载?其实有黑科技可以间接重载!

    ?? 操作符叫做 null-coalescing operator,即 null 合并运算符.如果此运算符的左操作数不为 null,则此运算符将返回左操作数:否则返回右操作数. 在微软的官方 C# 文 ...