小项目-数据爬取篇:scrapy-redis,手机网页,房天下租房信息,分布式,代理ip

对着背影说爱祢 提交于 2019-12-08 22:39:23

环境:

这回要用分布式:我使用两台电脑,一台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中的数据:

这里写图片描述

大功告成,本章结束,代码在这

数据都爬到了,下章要进行数据的清洗和分析。

版权所有,未经同意,不得转载

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!