要租房又不想自己找怎么办?用Scrapy爬取租房信息

2024-03-22 08:20

本文主要是介绍要租房又不想自己找怎么办?用Scrapy爬取租房信息,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

对于临近毕业的大学生(指自己)而言,怎样寻找便宜又实惠的房源无疑是人人都在关心的问题,今天就来看看怎样用爬虫技术快速抓取房源信息。

运行环境:

Python 3.6.3

Scrapy : 1.5.1

Twisted : 18.9.0

BeautifulSoup :4.6.3

fake_useragent :0.1.11

js2py :0.60

PyMySQL :0.9.3

难度:初级

网站总体难度较低,但联系方式的获取需要运用正则表达式/js引擎提取参数,携带参数和cookie后访问接口,因此对于爬虫初学者而言具有一定难度。

爬取到的数据:

(由于这个网站并不是把所有数据按顺序展示,而是每一页近乎随机的展示60条房源信息,且并不保证每一页数据不重复,因此一次运行无法爬取所有数据。本次运行选取的地区为广州,爬取的数据为2279条,时间为2019-05-01 23:53:13 ~ 2019-05-02 02:56:01。)

网站分析:

本次爬取的网站反爬手段相当单一,几乎所有数据都是静态加载,因此采取最初级的获取列表-获取单页-提取数据-翻页的流程即可。

在几个月前爬取这个网站时,房源联系人的联系方式都直接写在页面源码里,但几个月后再次爬取,发现联系方式变为了ajax动态加载,且ajax接口参数依赖于页面源码,导致速度被大大拖慢,目前没有找到可行的解决方法,希望有思路的朋友指点一二。

首先,创建scrapy项目:

scrapy startproject zufang

在zufang/settings.py里,填写如下内容:

HTTPERROR_ALLOWED_CODES = [404,400,502,503]
DOWNLOAD_TIMEOUT = 5
DOWNLOAD_DELAY = 1.5

● HTTPERROR_ALLOWED_CODES :允许被中间件处理的Response。状态码未在HTTPERROR_ALLOWED_CODES中声明的Response将被Scrapy引擎直接抛弃。

● DOWNLOAD_TIMEOUT :最大超时时间。超过DOWNLOAD_TIMEOUT值的请求将会触发twisted.internet.TimeoutError异常(可被Middleware的process_exception方法捕获)。

● DOWNLOAD_DELAY :下载延迟。Scrapy保证请求间隔不小于DOWNLOAD_DELAY。

在item.py中写入如下内容:

import scrapyclass ZufangItem(scrapy.Item):house_url = scrapy.Field()      #房屋链接house_name = scrapy.Field()     #房源名字price = scrapy.Field()          #房源租金house_type = scrapy.Field()     #房屋类型house_area = scrapy.Field()     #房屋面积rental_method = scrapy.Field()  #出租方式community = scrapy.Field()      #所在小区gender = scrapy.Field()         #性别要求deposit = scrapy.Field()        #押金方式contact = scrapy.Field()        #联系人phone = scrapy.Field()          #联系手机time = scrapy.Field()           #数据获取时间

如果想要了解一个租房信息,那么价格、面积、地址等信息显然是我们关注的部分,保存房屋链接可以让我们看中某个房源时方便的找到房源页面而不需要重新搜索,数据获取时间可以一定程度上体现数据的可用性——毕竟好的房源不会在网站上挂太久(笑)

在settings/spiders中创建文件houseSpider.py,并写入如下内容:

import json,time
from fake_useragent import UserAgent
import bs4
import zufang.items
import scrapy
import js2pyclass houseSpider(scrapy.Spider):name = "mainSpider"ua = UserAgent()urls = ["https://gz.zu.anjuke.com/fangyuan/p1/",] #起始url地址

这里要注意的是,文件名houseSpider.py和类名houseSpider都不影响爬虫调用,scrapy引擎只会根据类的name属性来查找、调用爬虫。

静态页面的分析为避免拖延篇幅就不做展开,直接贴代码:

    def start_requests(self):for url in self.urls :yield scrapy.Request(url = url,headers={"user-agent":self.ua.random},callback=self.parse_house_list,dont_filter=True)def parse_house_list(self, response):#解析网页。提取并请求其中所有的房源信息页。soup = bs4.BeautifulSoup(response.body.decode("utf8"), "lxml")info_list = soup.find_all(class_="zu-itemmod")url_list = [url.a["href"] for url in info_list]for url in url_list:yield scrapy.Request(url = url,headers={"user-agent":self.ua.random},callback=self.parse_house,dont_filter=True)#请求下一页。next_url = soup.find(class_="aNxt")if next_url != None :yield scrapy.Request(url=next_url["href"],headers={"user-agent": self.ua.random},callback=self.parse_house_list,dont_filter=True)def parse_house(self,response):cookie = response.headers["Set-Cookie"].decode("utf8")item = zufang.items.ZufangItem()soup = bs4.BeautifulSoup(response.body.decode("utf8"),"lxml")try :#房屋链接item["house_url"] = response.url#房源名字item["house_name"] = soup.find(class_="house-title").get_text()#租金item["price"] = soup.find(class_="price").em.get_text()#房屋类型item["house_type"] = soup.find_all(class_="house-info-item l-width")[0].find_all(name="span")[-1].get_text()#房屋面积item["house_area"] = soup.find(class_="info-tag no-line").em.get_text()#出租方式item["rental_method"] = soup.find(class_="full-line cf").find_all(name="span")[1].get_text()#所在小区item["community"] = soup.find_all(class_="house-info-item l-width")[2].a.get_text()#性别要求gender = soup.find_all(class_="house-info-item")[-1].find_all("span")[-1].get_text()if "小区" in gender :gender = "暂无"item["gender"] = gender#押金方式item["deposit"] = soup.find(class_="full-line cf").find_all("span")[1].get_text()#联系人item["contact"] = soup.find(class_="broker-name").get_text()

详细讲一点:联系方式。

在房源页面点击查看电话后,在Network页面搜索对应的号码,能看到一个专门用于获取电话的请求:

查看请求链接:

https://gz.zu.anjuke.com/v3/ajax/getBrokerPhone/?broker_id=5688071&token=0e03375fa6f3c05dd35e34aeb2b52590&prop_id=1320361235&prop_city_id=12&house_type=1&captcha=

忽略为空的captcha,共有五个参数,分别是broker_id,token,prop_id,prop_city_id,house_type。

初学者看到这么多参数可能就晕了,但其实只要在源码里ctrl+f就可以看到:

五个参数都在源码里写着,我们需要做得事情只是把它提取出来而已。

用正则提取显然会比较麻烦,这时候我们可以用到js2py了。使用方式非常简单,获取脚本文本,建立js2py.EvalJs对象,执行js文本后获取返回值,然后用模板替换出url,直接请求即可。

phone_template = "https://gz.zu.anjuke.com/v3/ajax/getBrokerPhone/?broker_id={broker_id}&token={token}&prop_id={prop_id}&prop_city_id={prop_city_id}&house_type={house_type}"js = soup.find_all(name="script")
context = js2py.EvalJs()
for i in js:if "brokerPhone" in i.get_text():context.execute(i.get_text())data_dict = getattr(context, "__Json4fe")broker_id = data_dict["getPhoneParam"]["broker_id"]token = data_dict["token"]prop_id = data_dict["prop_id"]prop_city_id = data_dict["prop_city_id"]house_type = data_dict["house_type"]yield scrapy.Request(url = self.phone_template.format(broker_id=broker_id,token=token,prop_id = prop_id,prop_city_id = prop_city_id,house_type = house_type,),headers={"user-agent":self.ua.random,"cookie" : cookie},meta = {"item":item,"url" :response.url,},callback=self.parse_phone,dont_filter=True)break

眼尖的读者应该看到了,headers里还额外提交了cookie参数,这个cookie是哪来的呢?

重新打开主页面的请求信息,可以看到Response Headers里set-cookie项:

这里设置的cookie也是获取联系号码的必备参数,实际效果类似于自己动手,用Python实现Pixiv动图下载器(附模拟登录流程)中Pixiv使用的Referer头。

在发送请求前保存cookie即可:

cookie = response.headers["Set-Cookie"].decode("utf8")

获取到联系号码后,将其保存,并同时保存当前时间,最后返回处理完成的item:

    def parse_phone(self,response):item = response.meta["item"]js = json.loads(response.text)item["phone"] = "".join(js["val"].split())item["time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())yield item

在项目目录下新建run.py文件,并输入以下内容:

from scrapy import cmdlineif __name__ == "__main__" :cmdline.execute("scrapy crawl mainSpider".split())

运行run.py即可直接启动Scrapy项目,而不用每次都切换到命令行界面了。

运行run.py,就可以在控制台中看到提取完成的数据了:

数据入库:

对于这种小规模的爬虫,数据入库部分非常简单,无需在意性能开销、网络传输开销等,直接在Pipelines中编写SaveDataPipeline即可。利用twisted.enterprise.adbapi实现异步插入:

from twisted.enterprise import adbapi
import zufang.settings as settingsclass SaveDataPipeline(object):#SQL命令模板insert_template = """INSERT INTO house_table VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);"""def __init__(self):self.dbpool = adbapi.ConnectionPool("pymysql",host=settings.sqlsetting["HOST"],port=settings.sqlsetting["PORT"],db=settings.sqlsetting["DB"],user=settings.sqlsetting["USER"],password=settings.sqlsetting["PASSWORD"],charset=settings.sqlsetting["CHARSET"],cp_reconnect=True)                        #自动检测失效连接并重连。def process_item(self, item, spider):query = self.dbpool.runInteraction(self.insert_data,item)query.addErrback(self.error_hander,item)return itemdef insert_data(self,cursor,item):house_id = item["house_url"].split("/")[-1].split("?")[0]cursor.execute(self.insert_template,[house_id,item["house_url"],item["house_name"],item["price"],item["house_type"],item["house_area"],item["rental_method"],item["community"],item["gender"],item["deposit"],item["contact"],item["phone"],item["time"]])def error_hander(self,failure,item):#由于网站数据的展示并非有序且唯一,所以主键重复是可能的。捕获后抛出即可。if "for key 'PRIMARY'" in str(failure) :print("主键重复:",item["house_url"].split("/")[-1])

其中数据库配置在settings中读取:

with open("DataBaseSettings.ini","r") as fp:sqlsetting = json.loads(fp.read())["default"]

DataBaseSettings.ini使用json格式存储配置,格式如下:

{"default":{"HOST":"your DB host","PORT":port,"DB":"house_data","USER":"your DB username","PASSWORD":"your DB password","CHARSET":"utf8"}
}

(个人认为将敏感配置单独编写是非常好的习惯,可以有效避免诸如“某网站管理员将数据库密码明文上传至GITHUB”一类的惨案发生。)


你可以在我的github中下载到本篇文章中的源码:OrsPced


警告

本篇文章原定发表日期为5月2日,因延迟发表,作者对文章中爬虫规则的有效性不做保证(但你可以留言让我改)


本文首发于知乎:https://zhuanlan.zhihu.com/p/71137275

END

这篇关于要租房又不想自己找怎么办?用Scrapy爬取租房信息的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/834913

相关文章

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

令人不想回忆的DDos

免责声明:本文仅做分享!!!    目录 DDos 介绍: 常见攻击方式: 基于TCP协议的攻击 基于icmp协议的攻击 web压力测试 攻击----> 1-工具脚本 MHDDos项目 LOIC(低轨道离子炮) HOIC(高轨道离子炮) HULK OWASP HTTP POST Tors Hammer 2-在线平台 防御----> 1-高防 2-C

Linux命令(11):系统信息查看命令

系统 # uname -a # 查看内核/操作系统/CPU信息# head -n 1 /etc/issue # 查看操作系统版本# cat /proc/cpuinfo # 查看CPU信息# hostname # 查看计算机名# lspci -tv # 列出所有PCI设备# lsusb -tv

【小迪安全笔记 V2022 】信息打点9~11

第9天 信息打点-CDN绕过篇&漏洞回链8接口探针&全网扫指&反向件 知识点: 0、CDN知识-工作原理及阻碍 1、CDN配置-域名&区域&类型 2、CDN绕过-靠谱十余种技战法 3、CDN绑定-HOSTS绑定指向访问 CDN 是构建在数据网络上的一种分布式的内容分发网。 CDN的作用是采用流媒体服务器集群技术,克服单机系统输出带宽及并发能力不足的缺点,可极大提升系统支持的并发流数目,减少或避

Weex入门教程之4,获取当前全局环境变量和配置信息(屏幕高度、宽度等)

$getConfig() 获取当前全局环境变量和配置信息。 Returns: config (object): 配置对象;bundleUrl (string): bundle 的 url;debug (boolean): 是否是调试模式;env (object): 环境对象; weexVersion (string): Weex sdk 版本;appName (string): 应用名字;

Python批量读取身份证信息录入系统和重命名

前言 大家好, 如果你对自动化处理身份证图片感兴趣,可以尝试以下操作:从身份证图片中快速提取信息,填入表格并提交到网页系统。如果你无法完成这个任务,我们将在“Python自动化办公2.0”课程中详细讲解实现整个过程。 实现过程概述: 模块与功能: re 模块:用于从 OCR 识别出的文本中提取所需的信息。 日期模块:计算年龄。 pandas:处理和操作表格数据。 PaddleOCR:百度的

linux上查看java最耗时的线程信息

找到JAVA进程pid ps -ef|grep java或则jps -mlv 找进行下耗时的线程TID 使用top -Hp pid可以查看某个进程的线程信息 -H 显示线程信息,-p指定pid top -Hp 10906 查看最耗时的 TID即线程id printf "%x\n" [tid] 转成16进制 java中的线程类相关信息 jstack 线程ID 可以查看某个线程的堆栈情况,特别对于h

在糖尿病患者信息管理系统中,导入病人信息功能!

在糖尿病患者信息管理系统中,导入病人信息功能!form表单提交数据(Excel文件),在后台得不到file文件,解决方法:         private File filePath; //文件         private String fileName; //文件名         private String fileType; //文件类型 注:上面filePath必须有,否则下面

前缀和 — 利用前缀信息解决子数组问题

【前缀和的核心思想是预先处理数组来快速计算任意子数组的和,基本上用于数组和序列问题。】 前缀和算法具体步骤 构造前缀和数组: 给定一个数组nums,其前缀和数组prex定义为prex[i]表示为数组nums从起始位置到第i个位置的元素累加和。构建前缀和公式: p r e x [ i ] = n u m s [ i ] ( i = = 0 ) p r e x [ i ] = p r e x