要租房又不想自己找怎么办?用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

相关文章

在C#中获取端口号与系统信息的高效实践

《在C#中获取端口号与系统信息的高效实践》在现代软件开发中,尤其是系统管理、运维、监控和性能优化等场景中,了解计算机硬件和网络的状态至关重要,C#作为一种广泛应用的编程语言,提供了丰富的API来帮助开... 目录引言1. 获取端口号信息1.1 获取活动的 TCP 和 UDP 连接说明:应用场景:2. 获取硬

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

C#实现获取电脑中的端口号和硬件信息

《C#实现获取电脑中的端口号和硬件信息》这篇文章主要为大家详细介绍了C#实现获取电脑中的端口号和硬件信息的相关方法,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 我们经常在使用一个串口软件的时候,发现软件中的端口号并不是普通的COM1,而是带有硬件信息的。那么如果我们使用C#编写软件时候,如

通过C#获取PDF中指定文本或所有文本的字体信息

《通过C#获取PDF中指定文本或所有文本的字体信息》在设计和出版行业中,字体的选择和使用对最终作品的质量有着重要影响,然而,有时我们可能会遇到包含未知字体的PDF文件,这使得我们无法准确地复制或修改文... 目录引言C# 获取PDF中指定文本的字体信息C# 获取PDF文档中用到的所有字体信息引言在设计和出

C#读取本地网络配置信息全攻略分享

《C#读取本地网络配置信息全攻略分享》在当今数字化时代,网络已深度融入我们生活与工作的方方面面,对于软件开发而言,掌握本地计算机的网络配置信息显得尤为关键,而在C#编程的世界里,我们又该如何巧妙地读取... 目录一、引言二、C# 读取本地网络配置信息的基础准备2.1 引入关键命名空间2.2 理解核心类与方法

使用Python检查CPU型号并弹出警告信息

《使用Python检查CPU型号并弹出警告信息》本教程将指导你如何编写一个Python程序,该程序能够在启动时检查计算机的CPU型号,如果检测到CPU型号包含“I3”,则会弹出一个警告窗口,感兴趣的小... 目录教程目标方法一所需库步骤一:安装所需库步骤二:编写python程序步骤三:运行程序注意事项方法二

PostgreSQL如何查询表结构和索引信息

《PostgreSQL如何查询表结构和索引信息》文章介绍了在PostgreSQL中查询表结构和索引信息的几种方法,包括使用`d`元命令、系统数据字典查询以及使用可视化工具DBeaver... 目录前言使用\d元命令查看表字段信息和索引信息通过系统数据字典查询表结构通过系统数据字典查询索引信息查询所有的表名可

业务中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