青客公寓挂牌房源分城市爬取

青客公寓分城市爬取

背景

搬家了,从原来的二房东搬到现在的二房东,只不过以前是个人二房东,现在是青客二房东管家,上班距离也从原来的10分钟增加到40分钟,时间成本的增加带来的是居住舒适度和居住环境的改善,对面青客这样一家专门做分散式集中公寓运营的企业,在交了两个月押金后还要做“租金贷”的冒险才能优惠一点,真想好好评估一下青客市场实力,预防其跑路,要研究别人,首先你得有别人的数据才行,故先简单爬取她家在各个城市挂牌房源瞅瞅。

思路

从其官网获取每套挂牌房源信息,并把相应的信息写入mysql,每个房源包含的字段有房源名称,房源编码,价格,朝向,楼层,小区名称,板块,区域,原详情页网址,地址等,由于其网页是分城市的挂牌房源信息,所以在程序设计的时候留两个入口,一个是城市简称,一个这个城市总网页数,先构造一级网页,然后通过一级网页获取每个房源的详情页,最后通过进入详情页获取想要的字段并组合成一条数据存入数据库mysql。

完整代码
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 26 09:31:55 2019
title:qingke365
@author: 帅帅de三叔
"""
import requests #导入请求模块
import time #导入时间功能模块
from tqdm import tqdm #导入进度条模块
from bs4 import BeautifulSoup #导入网页解析模块
import re #导入正则表达模块
header={"User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"} #请求头

print("connecting mysql……\n")
import pymysql #导入pymysql模块
db=pymysql.connect("localhost","root","123456","qingke",charset='utf8') #链接mysql数据库community
print("connect successfully,start creating table qingke_sh in database qingke\n")
cursor=db.cursor() #创建游标对象
cursor.execute("DROP TABLE IF EXISTS qingke_sh") #如果community_sh存在则drop掉
c_sql="""CREATE TABLE qingke_sh(
      district varchar(10),
      plate varchar(12),  
      title varchar(50),
      code varchar(12),
      price varchar(6),
      toward varchar(6),
      floor varchar(6),
      community varchar(8)    
            )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8"""
cursor.execute(c_sql) #创建一个表ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
print("table qingke_sh has been created\n")

def generate_page(city,num):#定义获取所有网页函数
    url="https://"+city+".qk365.com/list/p{}" #初始网址
    for url_next in range(1,int(num)+1):
        yield url.format(url_next) 

def get_detail_url(generate_page): #定义获取每一个房源详情页的链接入口
    response=requests.get(generate_page,headers=header) #发出一级请求
    #time.sleep(1) #进程挂起的时间
    soup=BeautifulSoup(response.text,'lxml') #解析网页
    contents=soup.find("ul",class_="easyList").find_all("li") #所有在售房源列表
    if contents: 
        for content in contents:
            #print(content)
            detail_url=content.find("a")['href'] #详情页网址
            print(detail_url)
            res=requests.get(detail_url,headers=header) #根据详情页二级请求网
            time.sleep(1) #进程挂起的时间
            soup=BeautifulSoup(res.text,'lxml') #解析详情页网页
            title=soup.find("div",class_="HouInfoL").find("h1",class_="houInfoTit").get_text().replace(" ","").strip() #去掉空格,并去掉前后空格
            code_pattern=re.compile("[a-zA-Z0-9]+") #用以正则房间编号
            code=re.search(code_pattern,soup.find("div",class_="survey-right fR").find("dd").get_text()).group(0) #房间编号
            pattern_price=re.compile("\d+") #用以正则价格
            price=re.search(pattern_price,soup.find("div",class_="survey-left fL").find("dd").get_text()).group(0) #价格
            toward=soup.find("div",class_="survey-left fL").findAll("dd")[2].find("em").get_text() #朝向
            floor=soup.find("div",class_="survey-left fL").findAll("dd")[3].get_text().replace(" ","").split(':')[-1] #楼层
            community=soup.find("div",class_="survey-left fL").findAll("dd")[4].find("a").get_text() #小区名称
            district=soup.find("div",class_="survey-right fR").findAll("dd")[3].find("a").get_text() #区域
            plate_content=soup.find("div",class_="survey-right fR").findAll("dd")[3] #板块
            plate=plate_content.findAll("a")[-1].get_text() #板块
            print(district,plate,title,code,price,toward,floor,community)
            insert_data=("INSERT INTO qingke_sh(district,plate,title,code,price,toward,floor,community)""VALUES(%s,%s,%s,%s,%s,%s,%s,%s)") #控制插入数据格式
            qingke_data=([district,plate,title,code,price,toward,floor,community]) #组成一条记录
            cursor.execute(insert_data,qingke_data) #执行插入操作
            db.commit() #主动提交
        
def main():#定义主函数
    city=input("please input the abbreviate name of city:") #输入城市简称
    num=input("please input total page num:") #输入总页数
    for page_link in tqdm(generate_page(city,num)):
        get_detail_url(page_link)

if __name__=="__main__":
    main()
代码解读

代码只需要输入每个城市的简称和总网页数,比如要获取上海的则输入sh,总页数254,然后根据generate_page函数构造出所有一级网页,紧接着用get_detail_url函数去获取每个房源的详情页,这里用generate_page作为其输入参数,然后利用get_detail_url函数进入详情页获取想要的字段并存入数据库,程序去获取青客在上海的每个房源的具体信息形成字段结构化数据存入mysql里面去,最后定义一个主函数main()把前面的几个函数功能整合起来。

后思考

首先,爬虫要考虑对方服务器的承受力,暴力爬虫不可取,可以通过time.sleep()挂起那么一小段时间;其次,要考虑程序的泛化能力,即今天可以用来爬青客,下次可以稍微修改一下可以用来爬其他类似的网站;这种程序设计有其好的一面就是分城市爬取,每次需要输入城市简称和总网页数,一个城市需要执行一次程序,一是一,二是二;也有不好的一面,比如每次都要去网页查找最后一页数字,属于半自动化,下一次可以尝试通过判断条件来定位当前爬取的是否是最后一页,而不再需要手动输入总页码数,同时对于这种大量读写操作,程序执行略慢,可以考虑多线程和多进程或者异步处理。下面利用asyncio模块进行异步爬取。修改代码如下

# -*- coding: utf-8 -*-
"""
Created on Fri Jul 26 09:31:55 2019
title:qingke365
@author: Uncle Three
"""
import requests #导入请求模块
import time #导入时间功能模块
import asyncio #导入异步IO模块
from bs4 import BeautifulSoup #导入网页解析模块
import re #导入正则表达模块
header={"User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"} #请求头
start_time=time.time() #起始时间
print("connecting mysql……\n")
import pymysql #导入pymysql模块
db=pymysql.connect("localhost","root","123456","qingke",charset='utf8') #链接mysql数据库community
print("connect successfully,start creating table qingke_wh in database qingke\n")
cursor=db.cursor() #创建游标对象
cursor.execute("DROP TABLE IF EXISTS qingke_wh") #如果community_sh存在则drop掉
c_sql="""CREATE TABLE qingke_wh(
      district varchar(10),
      plate varchar(12),  
      title varchar(50),
      code varchar(12),
      price varchar(6),
      toward varchar(6),
      floor varchar(6),
      community varchar(8),
      detail_url varchar(35)
            )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8"""
cursor.execute(c_sql) #创建一个表ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
print("table qingke_wh has been created\n")

def generate_page(city,num):#定义获取所有网页函数
    url="https://"+city+".qk365.com/list/p{}" #初始网址
    for url_next in range(1,int(num)+1):
        yield url.format(url_next) 

async def get_detail_item(generate_page): #定义获取每一个房源详情页的链接入口获取字段的异步函数
    response=requests.get(generate_page,headers=header) #发出一级请求
    soup=BeautifulSoup(response.text,'lxml') #解析网页
    contents=soup.find("ul",class_="easyList").find_all("li") #所有在售房源列表
    if contents: 
        for content in contents:
            #print(content)
            detail_url=content.find("a")['href'] #详情页网址
            #print(detail_url)
            res=requests.get(detail_url,headers=header) #根据详情页二级请求网
            time.sleep(1) #进程挂起的时间
            soup=BeautifulSoup(res.text,'lxml') #解析详情页网页
            title=soup.find("div",class_="HouInfoL").find("h1",class_="houInfoTit").get_text().replace(" ","").strip() #去掉空格,并去掉前后空格
            code_pattern=re.compile("[a-zA-Z0-9]+") #用以正则房间编号
            code=re.search(code_pattern,soup.find("div",class_="survey-right fR").find("dd").get_text()).group(0) #房间编号
            pattern_price=re.compile("\d+") #用以正则价格
            price=re.search(pattern_price,soup.find("div",class_="survey-left fL").find("dd").get_text()).group(0) #价格
            toward=soup.find("div",class_="survey-left fL").findAll("dd")[2].find("em").get_text() #朝向
            floor=soup.find("div",class_="survey-left fL").findAll("dd")[3].get_text().replace(" ","").split(':')[-1] #楼层
            community=soup.find("div",class_="survey-left fL").findAll("dd")[4].find("a").get_text() #小区名称
            district=soup.find("div",class_="survey-right fR").findAll("dd")[3].find("a").get_text() #区域
            plate_content=soup.find("div",class_="survey-right fR").findAll("dd")[3] #板块内容
            plate=plate_content.findAll("a")[-1].get_text() #板块
            print(district,plate,title,code,price,toward,floor,community,detail_url)
            insert_data=("INSERT INTO qingke_wh(district,plate,title,code,price,toward,floor,community,detail_url)""VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s)") #控制插入数据格式
            qingke_data=([district,plate,title,code,price,toward,floor,community,detail_url]) #组成一条记录
            cursor.execute(insert_data,qingke_data) #执行插入操作
            db.commit() #主动提交
            
def main():#定义主函数
    city=input("please input the abbreviate name of city:") #输入城市简称
    num=input("please input total page num:") #输入总页数
    loop=asyncio.get_event_loop() #创建一个异步循环
    tasks=[asyncio.ensure_future(get_detail_item(page_link)) for page_link in generate_page(city,num)] #通过 `ensure_future` 函数计划执行创建一个任务盒子tasks,每个任务为一页page的工作量
    #tasks = asyncio.gather(*tasks) #把多个协程任务交给 loop
    loop.run_until_complete(asyncio.wait(tasks)) #tasks接入loop中开始运行
    loop.close()
if __name__=="__main__":
    main()
end_time=time.time() #结束时间
print("run time:",end_time-start_time)

在写入mysql的时候如果报错及解决办法

  DataError: (1406, "Data too long for column 'community' at row 1")

在mysql命令里面执行下面语句

SET @@global.sql_mode= '';
  Warning: (1265, "Data truncated for column 'code' at row 1")

在mysql命令里面执行下面语句或者创建表的时候varchar()括号里面数字调大一些

code varchar(12) 变成code varchar(15)
免责申明

Python爬虫仅为学习交流,如有冒犯,请告知删。

上一篇:爬取西安雁塔区租房信息


下一篇:万字博文教你python爬虫必备Beautiful Soup库,看完还不会我把我女朋友都给你【❤️建议收藏系列❤️】