前言

这是本人的第一篇博客,感触还是很多的,最近在帮朋友做一个分布式爬虫的论文,遇到很多坑,不过已经一一填平,废话不多说啦。



分类

(1)主从分布式爬虫:
由一台master服务器, 来提供url的分发, 维护待抓取url的list。由多台slave服务器执行网页抓取功能,
slave所抽取的新url,一律由master来处理解析,而slave之间不需要做任何通信。
(2)对等分布式爬虫:
由多台相同的服务器集成,每台服务器可单独运作,完成爬虫工作,每台服务器之间的分工有一定的运算逻辑(ex:
hash),由运算(配置)的结果,来决定由那台服务器做抓取网页的工作。

具体查看:http://www.dataguru.cn/thread-529666-1-1.html
<http://www.dataguru.cn/thread-529666-1-1.html>


本文讲解第一种-主从式,只是简单的阐明,所以master端只负责爬取url保存到redis数据库,slave端取出redis里url队列进行爬取网页内容,解析并保存到mongodb数据库。




准备
python3(本人使用的是3.6版本)
scrapy
redis

mongodb

安装教程自行百度,使用到的python模块(这里是需要使用pip安装,最好是pip新版本):

scrapy-redis
pymongo


python链接mongodb例子:
import pymongo as pm host = 'localhost' port = 27017 # 链接数据库 client =
pm.MongoClient(host,port) # 选择db db = client.demo # 选择集合test #
注意这里的集合可以直接使用,如果没有mongodb会自动创建 db.test.insert({"name":"hello"}) client.close()



实现
我们以58同城为例,爬取二手房,爬取中间有些错误但不影响,由于网站整改302重定向了。
1. master端



spider文件
from scrapy.spider import CrawlSpider,Rule from scrapy.linkextractors import
LinkExtractor from master.items import MasterItem class myspider(CrawlSpider):
name = 'master' allowed_domains = ['58.com'] item = MasterItem() start_urls =
['http://cd.58.com/ershoufang/'] rules = (
Rule(LinkExtractor(allow=('http://cd.58.com/ershoufang/\d{14}x.shtml.*?',)),
callback='parse_item', follow=True), ) def parse_item(self,response): item =
self.item item['url'] = response.url return item

继承CrawlSpider可以遍历整个网站,Rule和LinkExtractor限制遍历那些网页,allow为允许的网址(正则表达式),callback回调函数,注意一定不能写默认的parse函数。

item文件
import scrapy class MasterItem(scrapy.Item): # define the fields for your item
here like: url = scrapy.Field() pass
只保存url

middleware文件
import random from .useragent import agents class UserAgentMiddleware(object):
def process_request(self, request, spider): agent = random.choice(agents)
referer = request.meta.get('referer', None) request.headers["User-Agent"] =
agent request.headers["Referer"] = referer

这里对默认的middleware文件进行修改,useragent是自建的文件,里面agents=['内容略']数组,字段代码表示为每次请求随机一个User-Agent(浏览器身份),一定程度避免认为是爬虫。

pipeline文件
import redis class MasterPipeline(object): def __init__(self): self.redis_url
= 'redis://123456:@localhost:6379/' self.r =
redis.Redis.from_url(self.redis_url,decode_responses=True) def
process_item(self, item, spider): self.r.lpush('myredis:start_urls',
item['url'])保存url到redis数据库,redis_url中123456为数据库密码,你的redis没有密码就不用写,第二种链接方式:
redis.Redis(host="localhost",port=6379)
默认使用db0,一定要使用db0,否则slave端取不到数据(可能由于本人才疏学浅没有找到链接其他db供slave端使用的方式)

setting文件
ROBOTSTXT_OBEY = False DOWNLOADER_MIDDLEWARES = {
'master.middlewares.UserAgentMiddleware': 543, } ITEM_PIPELINES = {
'master.pipelines.MasterPipeline': 300, }

注意这里只是部分代码不要覆盖上去,替换相同的地方即可。爬虫一开始会取得网站的robot.txt文件(也叫君子协议文件),得知那些网址可爬,我们设置ROBOTSTXT_OBEY=False不遵循他的协议。



2. slave端

spider文件
import re from scrapy_redis.spiders import RedisSpider from scrapy.http import
Request from slave.items import SlaveItem class myspider(RedisSpider): name =
'slave' item = SlaveItem() redis_key = 'myredis:start_urls' def parse(self,
response): item = self.item item['title'] =
response.xpath('//div[@class="house-title"]/h1/text()').extract()[0]
item['price'] =
response.xpath('//div[@id="generalSituation"]//li[1]/span[2]/text()').extract()[0]
item['type'] =
response.xpath("//div[@id='generalSituation']//li[2]/span[2]/text()").extract()[0]
item['area'] =
response.xpath("//div[@id='generalSituation']//li[3]/span[2]/text()").extract()[0]
item['direct'] =
response.xpath("//div[@id='generalSituation']//li[4]/span[2]/text()").extract()[0]
item['floor'] = response.xpath(
"//div[@id='generalSituation']//ul[@class='general-item-right']/li[1]/span[2]/text()").extract()[0]
item['decorat'] = response.xpath(
"//div[@id='generalSituation']//ul[@class='general-item-right']/li[2]/span[2]/text()").extract()[0]
item['start'] = response.xpath(
"//div[@id='generalSituation']//ul[@class='general-item-right']/li[4]/span[2]/text()").extract()[0]
item['village'] =
response.xpath("string(/html/body/div[4]/div[2]/div[2]/ul/li[1]/span[2])").extract()[0]
item['position'] =
response.xpath("string(/html/body/div[4]/div[2]/div[2]/ul/li[2]/span[2])").extract()[0]
item['phone'] =
response.xpath("//div[@id='houseChatEntry']//p[@class='phone-num']/text()").extract()[0]
txt = response.xpath("/html/head/script[1]/text()").extract()[0] pattern =
re.compile(".*?____json4fe.brokerUrl = '(.*?)';.*?", re.S) result =
re.findall(pattern, txt) yield Request("http://" + result[0],
callback=self.get_user) def get_user(self, response): item = self.item
item['user'] =
response.xpath("/html/body/div[2]/div[2]/div[1]/div[1]/div/div[1]/text()").extract()[0]
return item

这里不在讲解xpath的用法,告诉你们一个好方法,浏览器F12选中一个元素,右键->复制->xpath。用redis_key代替start_urls,‘myredis:start_urls’为redis数据库db0的键,就是我们master端保存的,直接使用默认回调函数parse。

item文件
import scrapy class SlaveItem(scrapy.Item): title = scrapy.Field() price =
scrapy.Field() type = scrapy.Field() area = scrapy.Field() direct =
scrapy.Field() floor = scrapy.Field() decorat = scrapy.Field() start =
scrapy.Field() village = scrapy.Field() position = scrapy.Field() user =
scrapy.Field() phone = scrapy.Field() pass
middleware文件(同master端)

pipeline文件
import pymongo as pm host = 'localhost' port = 27017 client =
pm.MongoClient(host,port) db = client.demo.tongcheng class
SlavePipeline(object): def process_item(self, item, spider):
db.insert(dict(item))
保存到mongodb数据库

setting文件

# 启用Redis调度存储请求队列 SCHEDULER = "scrapy_redis.scheduler.Scheduler"
#不清除Redis队列、这样可以暂停/恢复 爬取 # SCHEDULER_PERSIST = True # 确保所有的爬虫通过Redis去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #指定用于连接redis的URL(可选)
#如果设置此项,则此项优先级高于设置的REDIS_HOST 和 REDIS_PORT REDIS_URL =
'redis://[email protected]:6379/' BOT_NAME = 'slave' SPIDER_MODULES =
['slave.spiders'] NEWSPIDER_MODULE = 'slave.spiders' ROBOTSTXT_OBEY = False
DOWNLOADER_MIDDLEWARES = { 'slave.middlewares.UserAgentMiddleware': 543, }
ITEM_PIPELINES = { 'slave.pipelines.SlavePipeline': 300, }
加入和替换配置



结果

redis数据库



mongodb数据库





最后

本人也是小白,写的粗糙,有不懂或见解的地方一起交流。



ps:本文的github地址:https://github.com/tchtsm/scrapy
<https://github.com/tchtsm/scrapy>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信