厦门租房信息分析展示(pycharm+python爬虫+pyspark+pyecharts)

大数据学习路线图

【版权声明】版权所有,严禁转载,严禁用于商业用途,侵权必究。
作者:厦门大学计算机系2016级研究生 魏亮
指导老师:厦门大学计算机科学系数据库实验室 林子雨 博士/副教授
相关教材:林子雨、郑海山、赖永炫编著《Spark编程基础(Python版)》(访问教材官网
相关案例:基于Python语言的Spark数据处理分析案例集锦(PySpark)

本篇博客将实现一个系列程序,从厦门小鱼网爬取租房租金信息,然后利用spark的python版本进行简单分析,并利用echarts的python版本展示分析结果,此外还会简单介绍pycharm的工程建立,所以本篇将分为四个部分。

第一部分 建立工程文件fishRent

首先,关于pycharm的安装使用,以及python文件的import部分,可以参考作者的另一篇博客

创建工程文件

这里大家操作的时候只要注意这两点。第一:工程取名fishRent,第二:python版本选择3.6。请参考图片。

第二部分 python的爬虫(需要一点前端基础)

爬虫程序我们利用了BeautifulSoup这个库文件。首先在工程文件夹下创建名叫rentspider的Python文件。然后黏贴以下代码:

  1. # -*- coding: utf-8 -*-
  2. import requests
  3. from bs4 import BeautifulSoup
  4. import csv
  5.  
  6. # num表示记录序号
  7. Url_head = "http://fangzi.xmfish.com/web/search_hire.html?h=&hf=&ca=5920"
  8. Url_tail = "&r=&s=&a=&rm=&f=&d=&tp=&l=0&tg=&hw=&o=&ot=0&tst=0&page="
  9. Num = 0
  10. Filename = "rent.csv"
  11.  
  12.  
  13. # 把每一页的记录写入文件中
  14. def write_csv(msg_list):
  15. out = open(Filename, 'a', newline='')
  16. csv_write = csv.writer(out,dialect='excel')
  17. for msg in msg_list:
  18. csv_write.writerow(msg)
  19. out.close()
  20.  
  21.  
  22. # 访问每一页
  23. def acc_page_msg(page_url):
  24. web_data = requests.get(page_url).content.decode('utf8')
  25. soup = BeautifulSoup(web_data, 'html.parser')
  26. address_list = []
  27. area_list = []
  28. num_address = 0
  29. num_area = 0
  30. msg_list = []
  31.  
  32. # 得到了地址列表,以及区域列表
  33. for tag in soup.find_all(attrs="list-addr"):
  34. for em in tag:
  35. count = 0
  36. for a in em:
  37. count += 1
  38. if count == 1 and a.string != "[":
  39. address_list.append(a.string)
  40. elif count == 2:
  41. area_list.append(a.string)
  42. num_area += 1
  43. elif count == 4:
  44. if a.string is not None:
  45. address_list[num_address] = address_list[num_address] + "-" + a.string
  46. else:
  47. address_list[num_address] = address_list[num_address] + "-Null"
  48. num_address += 1
  49.  
  50. # 得到了价格列表
  51. price_list = []
  52. for tag in soup.find_all(attrs="list-price"):
  53. price_list.append(tag.b.string)
  54.  
  55. # 组合成为一个新的tuple——list并加上序号
  56. for i in range(len(price_list)):
  57. txt = (address_list[i], area_list[i], price_list[i])
  58. msg_list.append(txt)
  59.  
  60. # 写入csv
  61. write_csv(msg_list)
  62.  
  63.  
  64. # 爬所有的页面
  65. def get_pages_urls():
  66. urls = []
  67. # 思明可访问页数134
  68. for i in range(134):
  69. urls.append(Url_head + "1" + Url_tail + str(i+1))
  70. # 湖里可访问页数134
  71. for i in range(134):
  72. urls.append(Url_head + "2" + Url_tail + str(i+1))
  73. # 集美可访问页数27
  74. for i in range(27):
  75. urls.append(Url_head + "3" + Url_tail + str(i+1))
  76. # 同安可访问页数41
  77. for i in range(41):
  78. urls.append(Url_head + "4" + Url_tail + str(i+1))
  79. # 翔安可访问页数76
  80. for i in range(76):
  81. urls.append(Url_head + "5" + Url_tail + str(i+1))
  82. # 海沧可访问页数6
  83. for i in range(6):
  84. urls.append(Url_head + "6" + Url_tail + str(i+1))
  85. return urls
  86.  
  87.  
  88. def run():
  89. print("开始爬虫")
  90. out = open(Filename, 'a', newline='')
  91. csv_write = csv.writer(out, dialect='excel')
  92. title = ("address", "area", "price")
  93. csv_write.writerow(title)
  94. out.close()
  95. url_list = get_pages_urls()
  96. for url in url_list:
  97. try:
  98. acc_page_msg(url)
  99. except:
  100. print("格式出错", url)
  101. print("结束爬虫")
  102.  
Python
关于这个爬虫部分,我将做一点简单的介绍。下面这一段的代码是使用soup的方法,decode部分的编码我们可以在浏览器中使用检查的方法看html文件的header部分。举个简单的例子。在chrome浏览器中打开目标网页,然后右键选择“检查”,就可以看到以下结果。
  1. web_data = requests.get(page_url).content.decode('utf8')
  2. soup = BeautifulSoup(web_data, 'html.parser')
Python


同样的,我们可以利用这个方法来查看网页的元素,比如某一个具体的标签,如我们想爬取的list-word的标签。soup的方法,把html文件转化为一个dom的树型结构,所以在访问标签的时候可以使用find方法,也可以使用树型的方法,比如tag.em.a像这样。 然后小鱼网的访问有一个地方需要注意,你最多只能找到134页的搜索结果。也就是即使你信息量大于134页,也只能常规地看到134页,所以为了获取更多的数据量。我在实现中针对每一个区的租房信息进行爬取。经过人工测试,每个区的对应数量如下:思明(134),湖里(134),集美(27),同安(41),翔安(76),海沧(6)。爬完总计6000+条数据。

这里先不运行,因为我们这个工程最后会设计一个总的启动入口。

第三部分 pyspark的简单分析

pyspark是spark的python版本,就我个人看来,使用起来真的挺方便的,做个简单的分析程序的时候,我更偏爱这个版本。我们对于之前爬虫获取的数据会被存为“rent.csv”文件,我们这里要做的就是获取这些数据里,每个区的价格最大值,最小值,均值,中值。老样子,建立一个名为rent_analyse的Python文件。然后复制以下代码。
  1. # -*- coding: utf-8 -*-
  2. from pyspark.sql import SparkSession
  3. from pyspark.sql.types import IntegerType
  4.  
  5.  
  6. def spark_analyse(filename):
  7. print("开始spark分析")
  8. # 程序主入口
  9. spark = SparkSession.builder.master("local").appName("rent_analyse").getOrCreate()
  10. df = spark.read.csv(filename, header=True)
  11.  
  12. # max_list存储各个区的最大值,0海沧,1为湖里,2为集美,3为思明,4为翔安,5为同安;同理的mean_list, 以及min_list,approxQuantile中位数
  13. max_list = [0 for i in range(6)]
  14. mean_list = [1.2 for i in range(6)]
  15. min_list = [0 for i in range(6)]
  16. mid_list = [0 for i in range(6)]
  17. # 类型转换,十分重要,保证了price列作为int用来比较,否则会用str比较, 同时排除掉一些奇怪的价格,比如写字楼的出租超级贵
  18. # 或者有人故意标签1元,其实要面议, 还有排除价格标记为面议的
  19. df = df.filter(df.price != '面议').withColumn("price", df.price.cast(IntegerType()))
  20. df = df.filter(df.price >= 50).filter(df.price <= 40000)
  21.  
  22. mean_list[0] = df.filter(df.area == "海沧").agg({"price": "mean"}).first()['avg(price)']
  23. mean_list[1] = df.filter(df.area == "湖里").agg({"price": "mean"}).first()['avg(price)']
  24. mean_list[2] = df.filter(df.area == "集美").agg({"price": "mean"}).first()['avg(price)']
  25. mean_list[3] = df.filter(df.area == "思明").agg({"price": "mean"}).first()['avg(price)']
  26. mean_list[4] = df.filter(df.area == "翔安").agg({"price": "mean"}).first()['avg(price)']
  27. mean_list[5] = df.filter(df.area == "同安").agg({"price": "mean"}).first()['avg(price)']
  28.  
  29. min_list[0] = df.filter(df.area == "海沧").agg({"price": "min"}).first()['min(price)']
  30. min_list[1] = df.filter(df.area == "湖里").agg({"price": "min"}).first()['min(price)']
  31. min_list[2] = df.filter(df.area == "集美").agg({"price": "min"}).first()['min(price)']
  32. min_list[3] = df.filter(df.area == "思明").agg({"price": "min"}).first()['min(price)']
  33. min_list[4] = df.filter(df.area == "翔安").agg({"price": "min"}).first()['min(price)']
  34. min_list[5] = df.filter(df.area == "同安").agg({"price": "min"}).first()['min(price)']
  35.  
  36. max_list[0] = df.filter(df.area == "海沧").agg({"price": "max"}).first()['max(price)']
  37. max_list[1] = df.filter(df.area == "湖里").agg({"price": "max"}).first()['max(price)']
  38. max_list[2] = df.filter(df.area == "集美").agg({"price": "max"}).first()['max(price)']
  39. max_list[3] = df.filter(df.area == "思明").agg({"price": "max"}).first()['max(price)']
  40. max_list[4] = df.filter(df.area == "翔安").agg({"price": "max"}).first()['max(price)']
  41. max_list[5] = df.filter(df.area == "同安").agg({"price": "max"}).first()['max(price)']
  42.  
  43. # 返回值是一个list,所以在最后加一个[0]
  44. mid_list[0] = df.filter(df.area == "海沧").approxQuantile("price", [0.5], 0.01)[0]
  45. mid_list[1] = df.filter(df.area == "湖里").approxQuantile("price", [0.5], 0.01)[0]
  46. mid_list[2] = df.filter(df.area == "集美").approxQuantile("price", [0.5], 0.01)[0]
  47. mid_list[3] = df.filter(df.area == "思明").approxQuantile("price", [0.5], 0.01)[0]
  48. mid_list[4] = df.filter(df.area == "翔安").approxQuantile("price", [0.5], 0.01)[0]
  49. mid_list[5] = df.filter(df.area == "同安").approxQuantile("price", [0.5], 0.01)[0]
  50.  
  51. all_list = []
  52. all_list.append(min_list)
  53. all_list.append(max_list)
  54. all_list.append(mean_list)
  55. all_list.append(mid_list)
  56.  
  57. print("结束spark分析")
  58.  
  59. return all_list
Python
代码里有较为详细的注释,建议可以多看注释。这里也不能直接启动,我把它写成一个被调用的方式,返回值是一个list。

第四部 pyecharts画图

现在很多软件都会做python的版本,很恰好,echarts也有python版本,这里只是做个柱状图展示,大家有兴趣可以上网搜索一下更多的pyecharts的使用方法。建立一个名为 draw的python文件,复制以下代码。
  1. # -*- coding: utf-8 -*-
  2. from pyecharts import Bar
  3.  
  4.  
  5. def draw_bar(all_list):
  6. print("开始绘图")
  7. attr = ["海沧", "湖里", "集美", "思明", "翔安", "同安"]
  8. v0 = all_list[0]
  9. v1 = all_list[1]
  10. v2 = all_list[2]
  11. v3 = all_list[3]
  12.  
  13. bar = Bar("厦门市租房租金概况")
  14. bar.add("最小值", attr, v0, is_stack=True)
  15. bar.add("最大值", attr, v1, is_stack=True)
  16. bar.add("平均值", attr, v2, is_stack=True)
  17. bar.add("中位数", attr, v3, is_stack=True)
  18. bar.render()
  19. print("结束绘图")
Python
这个通过传入的list读取信息,并画出柱状图。最后启动的时候可能会报错,缺少pyecharts-snapshot插件。所以我们安装一下。点击File->Settings...,如下图:


点击红圈中的"+",搜索pyecharts-snapshot,选择安装,如下图。

最后 启动程序

建立一个名为run的python文件。复制以下代码。
  1. # -*- coding: utf-8 -*-
  2. import draw
  3. import rent_analyse
  4. import rentspider
  5.  
  6.  
  7. if __name__ == '__main__':
  8. print("开始总程序")
  9. Filename = "rent.csv"
  10. rentspider.run()
  11. all_list = rent_analyse.spark_analyse(Filename)
  12. draw.draw_bar(all_list)
  13. print("结束总程序")
Python
然后右键点击运行run程序。最后会在工程目录下得到两个文件,“rent.csv”以及“render.html”。在浏览器中打开render文件就可以看到以下的结果。




至此,本文结束。
文末附上代码文件,以及最后的输出结果文件压缩包fishRent
也可以点击链接查看echarts绘图结果render