环境:
这回要用分布式:我使用两台电脑,一台win10 系统,用来爬信息,一台ubuntu 16.04用来爬url,数据库redis,MongoDB都用的ubuntu上的,没有两台电脑在一个电脑上开两个爬虫也是一样的。
安装scrapy-redis
pip install scrapy-redis
scrapy-redis 和 scrapy用法基本相同,就是scrapy-redis跟redis库的粘合性更高一些,直接把url放到redis的key中,给出相应key,爬虫自己获取url进行爬取,url没了会等待这种永动式爬取,而且redis还会进行去重处理,避免数据爬取重复。
先创建个名字叫做zufang_scrapy的爬虫
scrapy startproject zufang_scrapy
在项目中创建zufang genspider:
scrapy genspider zufang "https://m.fang.com/zf/bj/"
pycharm打开,新建usa.py 和 begin.py 一个用来放user-agent 一个启动爬虫,上一章已经讲过了,结构如下:
settings加入:
#启用Redis调度存储请求队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
#确保所有的爬虫通过Redis去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_URL = 'redis://:你的密码@localhost:6379'
接下来分析网页,目标网站,手机端房天下租房,城市北京,手机端电脑访问上一章有教
这就是网站第一页了:
网页右键查看元素,发现这个跟智联招聘的不同,这个不是翻页,而是下拉触发请求,我们切换到网络,下拉看看情况:
发现了每次下拉翻页,都会在XHR中返回一个文件,看一下内容,正式我们想要的租房信息,但是这个链接怪怪的一堆乱码,复制在网页里打卡,发下没有内容,于是我打开了chrome,打开https://m.fang.com/zf/bj/?jhtype=zf这个链接后做同样的事儿,发现url是这样的:
虽然还是一些奇怪的编码,但是至少不是乱码了,我们复制下拉,网页打开以下:
是乱码,但是有内容了,我们在scrapy中试试:
class ZufangSpider(scrapy.Spider):
name = "zufang"
allowed_domains = ["m.fang.com"]
start_urls = ['https://m.fang.com/zf/?purpose=%D7%A1%D5%AC&jhtype=zf&city=%B1%B1%BE%A9&renttype=cz&c=zf&a=ajaxGetList&city=bj&r=0.743897934517868&page=3']
def parse(self, response):
print(response.body)
发现结果是二进制,就是编码问题:
print(response.body.decode("utf-8"))
decode一下试试:
成了!接下来就是找找我们要的url在哪,进行翻页就完成一半了。
看了一下url很好提取
就是class=”tongjihref”的a标签中的href
url = response.xpath('//a[@class="tongjihref"]/@href').extract()
运行一下,试试:
成了,哈哈!前面只要再加上https:,这样url就拿到了
然后是翻页,翻页更是简单,page后的参数就是页数,每爬一页+1就好了。
这个没有翻页那我们一共要翻多少页呢?
点击优选房源时出现的,于是一共是61574套,然后每页是16套,故3849页。
这里有两种方法,第一种是一开始就生成3849个url放到redis里,第二种方法是每爬一页生成一个到redis里。很明显第一种方法更好一些,因为这样就可以使用并发,速度能更快一些。
# -*- coding: utf-8 -*-
import redis
from scrapy_redis.spiders import RedisSpider
from ..settings import *
class ZufangSpider(RedisSpider): #这里是RedisSpider
name = "zufang_up"
allowed_domains = ["m.fang.com"]
base_url = 'https://m.fang.com/zf/?purpose=%D7%A1%D5%AC&jhtype=zf&city=%B1%B1%BE%A9&renttype=cz&c=zf&a=ajaxGetList&city=bj&r=0.743897934517868&page='
rds = redis.from_url(REDIS_URL,db=0,decode_responses=True)
for i in range(1, 3849):
start_url = base_url+str(i)
rds.rpush("zufang:start_urls", start_url)
redis_key = "zufang:start_urls"
def parse(self, response):
urls = response.xpath('//a[@class="tongjihref"]/@href').extract()
for url in urls:
url = "https:"+url
self.rds.rpush('zufang:house_urls', url)
代理ip中间件如下:
class HttpProxymiddleware(object):
#一些异常情况汇总
EXCEPTIONS_TO_CHANGE = (
defer.TimeoutError, TimeoutError, ConnectionRefusedError, ConnectError, ConnectionLost, TCPTimedOutError,
ConnectionDone)
def __init__(self):
#链接redis数据库
self.rds = redis.from_url(REDIS_URL,db=0,decode_responses=True) ##decode_responses设置取出的编码为str
def process_request(self, request, spider):
#拿出全部key,随机选取一个键值对
keys = self.rds.hkeys("xila_hash")
key = random.choice(keys)
#用eval函数转换为dict
proxy = eval(self.rds.hget("xila_hash",key))
logger.info("-----------------"+str(proxy)+"试用中------------------------")
#将代理ip 和 key存入mate
request.meta["proxy"] = proxy["ip"]
request.meta["accountText"] = key
def process_response(self, request, response, spider):
http_status = response.status
#根据response的状态判断 ,200的话ip的times +1重新写入数据库,返回response到下一环节
if http_status == 200:
key = request.meta["accountText"]
proxy = eval(self.rds.hget("xila_hash",key))
proxy["times"] = proxy["times"] + 1
self.rds.hset("xila_hash",key,proxy)
return response
#403有可能是因为user-agent不可用引起,和代理ip无关,返回请求即可
elif http_status == 403:
logging.warning("#########################403重新请求中############################")
return request.replace(dont_filter=True)
#其他情况姑且被判定ip不可用,times小于10的,删掉,大于等于10的暂时保留
else:
key = request.meta["accountText"]
if self.rds.hget("xila_hash", key):
proxy = eval(self.rds.hget("xila_hash", key))
if proxy["times"] < 10:
self.rds.hdel("xila_hash",key)
logging.warning("#################" + str(proxy) + "不可用,已经删除########################")
return request.replace(dont_filter=True)
def process_exception(self, request, exception, spider):
#其他一些timeout之类异常判断后的处理,ip不可用删除即可
if isinstance(exception, self.EXCEPTIONS_TO_CHANGE) \
and request.meta.get('proxy', False):
key = request.meta["accountText"]
if self.rds.hget("xila_hash", key):
proxy = eval(self.rds.hget("xila_hash", key))
if proxy["times"] < 10:
self.rds.hdel("xila_hash", key)
logging.warning("#################" + str(proxy) + "不可用,已经删除########################")
logger.debug("Proxy {} 链接出错 {}.".format(request.meta['proxy'], exception))
return request.replace(dont_filter=True)
下载url的爬虫已经写好,这时我们在spider目录下新建一个爬虫zufang_down.py,之前的爬虫rename为zufang_up.py做以区分
我们来看一下每一套房的信息:
根据需要的信息,编写item:
# -*- coding: utf-8 -*-
import scrapy
class ZufangScrapyItem(scrapy.Item):
#标题
title = scrapy.Field()
# 区(朝阳)
area = scrapy.Field()
# 区域 (劲松)
location = scrapy.Field()
#小区 (劲松五区)
housing_estate = scrapy.Field()
# 租金
rent = scrapy.Field()
# 建筑面积
floor_area = scrapy.Field()
# 户型
house_type = scrapy.Field()
# 楼层
floor = scrapy.Field()
# 朝向
orientations = scrapy.Field()
# 装修
decoration = scrapy.Field()
# 房源描述
house_info = scrapy.Field()
# 标签
house_tags = scrapy.Field()
配合火狐浏览器fire path 获取xpath位置:
def parse(self, response):
item = ZufangScrapyItem()
item["title"] = response.xpath('//*[@class="xqCaption mb8"]/h1/text()')[0].extract()
item["area"] = response.xpath('//*[@class="xqCaption mb8"]/p/a[2]/text()')[0].extract()
item["location"] = response.xpath('//*[@class="xqCaption mb8"]/p/a[3]/text()')[0].extract()
item["housing_estate"] = response.xpath('//*[@class="xqCaption mb8"]/p/a[1]/text()')[0].extract()
item["rent"] = response.xpath('//*[@class="f18 red-df"]/text()')[0].extract()
item["rent_type"] = response.xpath('//*[@class="f12 gray-8"]/text()')[0].extract()[1:-1]
item["floor_area"] = response.xpath('//*[@class="flextable"]/li[3]/p/text()')[0].extract()[:-2]
item["house_type"] = response.xpath('//*[@class="flextable"]/li[2]/p/text()')[0].extract()
item["floor"] = response.xpath('//*[@class="flextable"]/li[4]/p/text()')[0].extract()
item["orientations"] = response.xpath('//*[@class="flextable"]/li[5]/p/text()')[0].extract()
item["decoration"] = response.xpath('//*[@class="flextable"]/li[6]/p/text()')[0].extract()
item["house_info"] = response.xpath('//*[@class="xqIntro"]/p/text()')[0].extract()
item["house_tags"] = ",".join(response.xpath('//*[@class="stag"]/span/text()').extract())
yield item
下载管道代码:
import pymongo
class ZufangScrapyPipeline(object):
def __init__(self):
# 这里的mongodb需要设置,auth权限啥啥的,比较麻烦,百度找找吧,我更更改了端口
client = pymongo.MongoClient("mongodb://auth名称:你的密码@192.168.3.7:2018",connect=False)
db = client["test"]
self.collection = db["zufang-beijing"]
def process_item(self, item, spider):
content = dict(item)
self.collection.insert(content)
print("###################已经存入MongoDB########################")
return item
def close_spider(self, spider):
pass
settings配置好管道,就完成了,同时运行up 和down,看看效果吧~
看看结果,redis中的url:
获取详细信息:
写入MongoDB中的数据:
大功告成,本章结束,代码在这。
数据都爬到了,下章要进行数据的清洗和分析。
版权所有,未经同意,不得转载
来源:CSDN
作者:泛泛之素
链接:https://blog.csdn.net/tonydz0523/article/details/78941155