房天下爬虫可分布式

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 需要观察房天下url的构造,本次爬取的是新房和二手房两个栏目的具体字段。涉及到的知识点有url的拼接,具体字段的解析清洗,页面不规整的情况下,怎样提取。
  • 需要观察房天下url的构造,本次爬取的是新房和二手房两个栏目的具体字段。
  • 涉及到的知识点有url的拼接,具体字段的解析清洗,页面不规整的情况下,怎样提取。
  • 分布式部署的相关操作
  • 在爬的时候建议网页延迟多一些。
    fangtianxia.py文件
import scrapy,re
from fang.items import NewHouseItem,ESFHouseItem

class FangtianxiaSpider(scrapy.Spider):
    name = 'fangtianxia'
    allowed_domains = ['fang.com']
    start_urls = ['http://www.fang.com/SoufunFamily.htm']

    def parse(self, response):
        trs = response.xpath('//div[@class="outCont"]//tr')
        province = None # 首先设为没有值,下方判断有值在赋给province
        for tr in trs:
            tds = tr.xpath('.//td[not(@class)]')
            province_td = tds[0] # 提取省份,由于省份不是每一行都有的,所以要过滤一下
            province_text = province_td.xpath('.//text()').get() # 没有省份的那一行会有空格
            province_text = re.sub(r'\s','',province_text) # 用sub替换一下,好判断
            if province_text:
                province = province_text # 如果有值,就赋给province
            if '其它' in province: # 不提取海外的
                continue
            city_id = tds[1] # 接下来提取城市链接和城市名称
            city_links = city_id.xpath('.//a')
            for city_link in city_links:
                city_url = city_link.xpath('.//@href').get()
                city = city_link.xpath('.//text()').get()

                # 构建新房和二手房的url
                url_module = city_url.split('fang')
                prefix = url_module[0]
                domain = url_module[1]
                # 北京特殊,特殊处理一下
                if 'bj' in prefix:
                    newhouse_url = 'http://' + 'newhouse.fang' + domain + 'house/s/'
                    esf_url = 'http://' + 'esf.fang' + domain
                else:
                # 构建新房的url
                    newhouse_url = prefix + 'newhouse.fang' + domain + 'house/s/'
                    # 构建二手房的url
                    esf_url = prefix + 'esf.fang' + domain
                # meta里面可以携带一些参数信息放到Request里面,在callback函数里面通过response获取
                yield scrapy.Request(url=newhouse_url,callback=self.parse_newhouse,meta={'info':(province,city)})

                yield scrapy.Request(url=esf_url,callback=self.parse_esf,meta={'info':(province,city)})


    def parse_newhouse(self,response):
        # 解析新房具体字段
        # meta里面可以携带一些参数信息放到Request里面,在callback函数里面通过response获取
        province,city = response.meta.get('info')
        lis = response.xpath('//div[contains(@class,"nl_con")]/ul/li')
        for li in lis:
            name = li.xpath(".//div[contains(@class,'house_value')]//div[@class='nlcd_name']/a/text()").get()
            if name:
                name = re.sub(r"\s","",name)
            house_type_list = li.xpath('.//div[contains(@class,"house_type")]/a/text()').getall()
            #house_type_list = list(map(lambda x:x.replace(' ',''),house_type_list))
            house_type_list = list(map(lambda x:re.sub(r'/s','',x),house_type_list))
            rooms = list(filter(lambda x:x.endswith('居'),house_type_list))
            area = ''.join(li.xpath('.//div[contains(@class,"house_type")]/text()').getall())
            area = re.sub(r'\s|-|/','',area)
            address = li.xpath('.//div[@class="address"]/a/@title').get()
            # district_text = ''.join(li.xpath('.//div[@class="address"]/a//text()').getall())
            # district = re.search(r'.*\[(.+)\].*',district_text).group(1)
            sale = li.xpath(".//div[contains(@class,'fangyuan')]/span/text()").get()
            price = "".join(li.xpath(".//div[@class='nhouse_price']//text()").getall())
            price = re.sub(r"\s|广告", "", price)
            # 详情页url
            origin_url = li.xpath(".//div[@class='nlcd_name']/a/@href").get()

            item = NewHouseItem(name=name,rooms=rooms.get(),area=area,address=address,
                                sale=sale,price=price,origin_url=origin_url,province=province,city=city)
            yield item

            # 下一页
            # next_url = response.xpath("//div[@class='page']//a[@class='next']/@href").get()
            # if next_url:
            #     yield scrapy.Request(url=response.urljoin(next_url),
            #                          callback=self.parse_newhouse,
            #                          meta={'info': (provice, city)}
            #                          )

    def parse_esf(self, response):
        # 二手房
        province, city = response.meta.get('info')
        dls = response.xpath("//div[@class='shop_list shop_list_4']/dl")
        for dl in dls:
            item = ESFHouseItem(province=province,city=city)
            name = dl.xpath(".//span[@class='tit_shop']/text()").get()
            if name:
                infos = dl.xpath(".//p[@class='tel_shop']/text()").getall()
                infos = list(map(lambda x: re.sub(r"\s", "", x), infos))
                for info in infos:
                    if "厅" in info:
                        item["rooms"] = info
                    elif '层' in info:
                        item["floor"] = info
                    elif '向' in info:
                        item['toward'] = info
                    elif '㎡' in info:
                        item['area'] = info
                    elif '年建' in info:
                        item['year'] = re.sub("年建", "", info)
                item['address'] = dl.xpath(".//p[@class='add_shop']/span/text()").get()
                # 总价
                item['price'] = "".join(dl.xpath(".//span[@class='red']//text()").getall())
                # 单价
                item['unit'] = dl.xpath(".//dd[@class='price_right']/span[2]/text()").get()
                item['name'] = name
                detail = dl.xpath(".//h4[@class='clearfix']/a/@href").get()
                item['origin_url'] = response.urljoin(detail)
                yield item
        # 下一页
        # next_url = response.xpath("//div[@class='page_al']/p/a/@href").get()
        # if next_url:
        #     yield scrapy.Request(url=response.urljoin(next_url),
        #                          callback=self.parse_esf,
        #                          meta={'info': (provice, city)}
        #                          )

item.py文件

import scrapy
from scrapy import Field

class NewHouseItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 省份
    province = Field()
    # 城市
    city = Field()
    # 小区名字
    name = Field()
    # 价格
    price = Field()
    # 几居室,这是一个列表
    rooms = Field()
    # 面积
    area = Field()
    # 地址
    address = Field()


    sale = Field()
    # 房天下详情url
    origin_url = Field()


class ESFHouseItem(scrapy.Item):
    # 省份
    province = Field()
    # 城市
    city = Field()
    # 小区名字
    name = Field()
    # 几室几厅
    rooms = Field()
    # 层
    floor = Field()
    # 朝向
    toward = Field()
    # 年代
    year = Field()
    # 地址
    address = Field()
    # 建筑面积
    area = Field()
    # 总价
    price = Field()
    # 单价
    unit = Field()
    # 详情页url
    origin_url = Field()

settings.py文件

ROBOTSTXT_OBEY = False

DOWNLOAD_DELAY = 1

from fake_useragent import UserAgent
ua = UserAgent().random

DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
  'User-Agent':ua
}

ITEM_PIPELINES = {
 'fang.pipelines.FangPipeline': 300,
  'fang.pipelines.MongoPipeline': 400,
}

MONGO_URI = 'localhost'
MONGO_DB = 'fangtianxia'

pipelines.py文件

from scrapy.exporters import JsonLinesItemExporter

class FangPipeline(object):
    def __init__(self):
        self.newhouse_fp = open('newhouse.json','wb')
        self.esfhouse_fp = open('esfhouse.json','wb')
        self.newhouse_exporter = JsonLinesItemExporter(self.newhouse_fp,ensure_ascii=False)
        self.esfhouse_exporter = JsonLinesItemExporter(self.esfhouse_fp,ensure_ascii=False)

    def process_item(self, item, spider):
        self.newhouse_exporter.export_item(item)
        self.esfhouse_exporter.export_item(item)
        return item

    def close_spider(self,spider):
        self.newhouse_fp.close()
        self.esfhouse_fp.close()



# 保存到mongodb
class MongoPipeline(object):
    def __init__(self,mongo_uri,mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
    @classmethod
    def from_crawler(cls,crawler):
        return cls(
            mongo_uri = crawler.settings.get('MONGO_URI'),
            mongo_db = crawler.settings.get('MONGO_DB')
        )
    def open_spider(self,spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]
    def process_item(self,item,spider):
        name = item.__class__.__name__
        self.db[name].insert(dict(item))
        return item
    def close_spider(self,spider):
        self.client.close()

改造成分布式爬虫

首先安装pip install scrapy-redis
要将一个Scrapy项目变成一个Scrapy-redis项目只需修改以下三点就可以了:

  1. 将爬虫的类从 scrapy.Spider 变成 scrapy_redis.spiders.RedisSpider;或者是从 scrapy.CrawlSpider 变成 scrapy_redis.spiders.RedisCrawlSpider。
    拿上面的例子来说就是在 fangtianxia.py文件中
from scrapy_redis.spiders import RedisSpider

class FangtianxiaSpider(RedisSpider):
    name = 'fangtianxia'
    allowed_domains = ['fang.com']
    # start_urls = ['http://www.fang.com/SoufunFamily.htm']
    redis_key = "fang:start_urls"

  1. 将爬虫中的start_urls删掉。增加一个redis_key="xxx"。这个redis_key是为了以后在redis中控制爬虫启动的。爬虫的第一个url,就是在redis中通过这个发送出去的。
  2. 更改scrapy的调度器,用redis实现的调度器。url去重的工作也交由redis完成,爬取的数据共享一下,存储到redis。在配置文件中增加如下配置:
    # Scrapy-Redis相关配置
    # 确保request存储到redis中
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"

    # 确保所有爬虫共享相同的去重指纹
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

    # 设置redis为item pipeline
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 300
    }

    # 在redis中保持scrapy-redis用到的队列,不会清理redis中的队列,从而可以实现暂停和恢复的功能。
    SCHEDULER_PERSIST = True

    # 设置连接redis信息
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379

运行爬虫:

  1. 在爬虫服务器上。进入爬虫文件所在的路径,然后输入命令:scrapy runspider [爬虫名字]。
  2. 在Redis服务器上,推入一个开始的url链接:redis-cli> lpush [redis_key] start_url开始爬取。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
数据采集 消息中间件 安全
.NET使用分布式网络爬虫框架DotnetSpider快速开发爬虫功能
.NET使用分布式网络爬虫框架DotnetSpider快速开发爬虫功能
112 0
|
数据采集 NoSQL Redis
python爬虫:scrapy-redis实现分布式爬虫
python爬虫:scrapy-redis实现分布式爬虫
220 0
|
数据采集 大数据 Python
2个月精通Python爬虫——3大爬虫框架+6场实战+分布式爬虫,包教包会
阿里云大学在线工作坊上线,原理精讲+实操演练,让你真正掌握云计算、大数据技能。 在第一批上线的课程中,有一个Python爬虫的课程,畅销书《精通Python网络爬虫》作者韦玮,带你两个月从入门到精通。
6907 0
|
数据采集 搜索推荐 Python
24、Python快速开发分布式搜索引擎Scrapy精讲—爬虫和反爬的对抗过程以及策略—scrapy架构源码分析图
【百度云搜索:http://www.lqkweb.com】 【搜网盘:http://www.swpan.cn】 1、基本概念 2、反爬虫的目的 3、爬虫和反爬的对抗过程以及策略 scrapy架构源码分析图
6020 0
|
数据采集 搜索推荐 前端开发
23、 Python快速开发分布式搜索引擎Scrapy精讲—craw母版l创建自动爬虫文件—以及 scrapy item loader机制
转: http://www.bdyss.cn http://www.swpan.cn 用命令创建自动爬虫文件 创建爬虫文件是根据scrapy的母版来创建爬虫文件的 scrapy genspider -l  查看scrapy创建爬虫文件可用的母版 Available templates:母版说明  ...
1179 0
21、 Python快速开发分布式搜索引擎Scrapy精讲—爬虫数据保存
转: 【http://bdy.lqkweb.com】 【http://www.swpan.cn】 注意:数据保存的操作都是在pipelines.py文件里操作的 将数据保存为json文件 spider是一个信号检测 # -*- coding: utf-8 -*- # Define your it.
1262 0
|
数据采集 前端开发 搜索推荐
20、 Python快速开发分布式搜索引擎Scrapy精讲—编写spiders爬虫文件循环抓取内容—meta属性返回指定值给回调函数—Scrapy内置图片下载器
编写spiders爬虫文件循环抓取内容 Request()方法,将指定的url地址添加到下载器下载页面,两个必须参数,  参数:  url='url'  callback=页面处理函数  使用时需要yield Request() parse.
1325 0
|
数据采集 NoSQL 调度
scrapy框架通用爬虫、深度爬虫、分布式爬虫、分布式深度爬虫,源码解析及应用
scrapy框架是爬虫界最为强大的框架,没有之一,它的强大在于它的高可扩展性和低耦合,使使用者能够轻松的实现更改和补充。 其中内置三种爬虫主程序模板,scrapy.Spider、RedisSpider、CrawlSpider、RedisCrawlSpider(深度分布式爬虫)分别为别为一般爬虫、分布式爬虫、深度爬虫提供内部逻辑;下面将从源码和应用来学习, scrapy.
2664 0
|
2月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?