自己动手搭建一个简单的基于Hadoop的离线分析系统之一——网络爬虫

之前对大数据颇感兴趣,在学习了一个月的相关原理和应用后,感觉自己需要动手一个实战项目来巩固之前学到的东西,加之很早之前就接触过一些爬虫知识,因此利用手上现有的资源(一台笔记本电脑)来搭建一个关于房屋租赁的简单的基于Hadoop的离线分析系统,其中包含了爬虫、HDFS、MapReduce、MySQL以及hive的简单应用。
由于手上硬件资源着实有限,该系统是实际应用系统的超级简化版,旨在对大数据的一部分相关知识综合起来做一个简单应用,请大神勿喷!

项目整体框架

一、基本环境

  为了避免后面出现各种环境问题,这里首先给出我的基本环境配置信息:
1. Windows
  a. Window10 64位操作系统
  b. Python3.7
  c. jdk1.7.0_80
  d. maven3.6.0
  e. VMware Workstation 14 Pro
  f. SecureCRT 8.0
2. Linux
  a. Centos7 64位
  b. Python3.6.5
  c. jdk1.7.0_80
  d. Hadoop2.6.5
  e. hive1.2.1
  f. MySQL5.7.24

二、待爬信息

  我选择的房屋租赁信息网站是小猪短租,该网站没有使用大量的JS渲染以及异步加载方式等反爬取手段,即使IP被封也可以通过输入验证码来解封,并不影响接下来一段时间的爬取。
  待爬信息有:出租房屋所在省、市、区,起步价格,房屋面积,适宜居住的人数,出租标题信息,详细地址,如下图所示。

三、爬虫代码(For Windows)

'''
@author: Ἥλιος
@CSDN:https://blog.csdn.net/qq_40793975/article/details/82734297
Platform:Windows Python3
'''
print(__doc__)from bs4 import BeautifulSoup
import requests
import re
import time
import random
import sys
import getopturl = 'http://sh.xiaozhu.com/'
proxies = {"http": "123.114.202.119:8118"}  # 代理IP
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0'
}   # 消息头
MunicList = ['sh', 'tj', 'cq', 'bj']    # 直辖市列表def get_page_links(url=None, page_links=None, label=0):"""爬取某个网页上的全部房屋租赁链接:param url: 网页链接:param page_links: 全部房屋租赁链接:param label: 标志位,该网页是第一页为1,否则为1:return: 状态码,0爬取成功,1IP被封导致爬取失败,2爬取成功且当前网页为最后一页"""sec = (random.random() + 0) * 10time.sleep(sec)  # 请求延时wb_data = requests.get(url, headers=header)if wb_data.status_code == 202:  # 页面响应状态202,IP被封print("IP blocked")return 1soup = BeautifulSoup(wb_data.text, 'lxml')links = soup.select('.pic_list > li > a:nth-of-type(1)')for link in links:page_link = link.get('href')page_links.append(page_link)info = soup.select('a.font_st')if len(info) <= 1 and label == 0:  # 判断当前页是不是最后一页,不检查第一页print("Last page")return 2return 0def detail2adress(str=None):"""使用正则表达式提取详细地址(非直辖市)中的省或行政区、市或自治区、区或县:param str: 详细地址:return: 省或行政区、市或自治区、区或县组成的列表"""result_default = [None, None, None]if str is None:return result_defaultresult = re.search('(?P<province>[^省]+省|[^行政区]+行政区)(?P<city>[^市]+市|[^自治区]+自治区)(?P<county>[^县]+县|[^区]+区)', str)if result is None:return result_defaultreturn list(result.groups())def detail2adress_Munic(str=None):"""使用正则表达式提取详细地址(直辖市)中的省或行政区、市或自治区、区或县:param str: 详细地址:return: 省或行政区、市或自治区、区或县组成的列表"""result_default = [None, None, None]if str is None:return result_defaultresult = re.search('(?P<city>[^市]+市)(?P<county>[^区]+区)', str)if result is None:return result_defaultresult = list(result.groups())result_default[0] = result[0]result_default[1:3] = result[:]return result_defaultdef get_rental_information(url=None, Munic=0):"""根据链接爬取某个房屋租赁信息:param url: 待爬取房屋租赁信息的链接:param Munic: 标志位,1是直辖市,否则为0:return: 房屋租赁信息"""sec = (random.random() + 0) * 10time.sleep(sec)  # 请求延时wb_data = requests.get(url, headers=header)if wb_data.status_code == 202:print("IP blocked")return 1soup = BeautifulSoup(wb_data.text, 'lxml')address = soup.select('.pho_info > p')[0].get('title')price = soup.select('.day_l > span:nth-of-type(1)')[0].textsize = soup.select('.border_none > p')[0].textnumber = soup.select('.h_ico2')[0].texttitle = soup.select('.pho_info > h4:nth-of-type(1) > em:nth-of-type(1)')[0].textpattern_size = re.compile(r'\d+')   # 查找数字pattern_number = re.compile(r'\d+')   # 查找数字size = pattern_size.findall(size.split(' ')[0])[0]number = pattern_number.findall(number)[0]data = {'address': detail2adress_Munic(address) if Munic else detail2adress(address),'price': int(price),'size': int(size),'number': int(number),'detail_address': address,'title': title}return datadef get_area_page_links(area=None):"""爬取某所有网页上的全部房屋租赁链接:param area: 这些网页所属的地区:return: 全部房屋租赁链接"""sec = (random.random() + 1) * 10time.sleep(sec)page_links = []for i in range(100):label = 0if i + 1 == 1:label = 1url = 'http://{}.xiaozhu.com/'.format(area)else:url = 'http://{}.xiaozhu.com/search-duanzufang-p{}-0/'.format(area, i + 1)res = get_page_links(url, page_links, label)print("Area: " + area + " ,Page: " + str(i+1))print(len(page_links))if res != 0:breakreturn page_linksdef get_area_rental_information(area=None):"""根据该地区的全部房屋租赁链接爬取房屋租赁信息:param area: 这些房屋租赁链接所属的地区:return: 状态码,0爬取成功, 1IP被封导致爬取失败"""Munic = 0if area in MunicList:Munic = 1area_page_links = get_area_page_links(area)filename = 'F:\\{}_rental_information.txt'.format(area)  # 租赁信息存储路径try:fw = open(filename, 'w', encoding='utf-8')except IOError:print("Fail in open file" + filename)else:link_num = 0for page_link in area_page_links:link_num += 1rental_data = get_rental_information(page_link, Munic)if rental_data == 1:fw.flush()fw.close()return 1line = rental_data['address'][0] + '\t' + rental_data['address'][1] \+ '\t' + rental_data['address'][2] + '\t' + str(rental_data['price'])\+ '\t' + str(rental_data['size']) + '\t' + str(rental_data['number']) + '\t' \+ rental_data['detail_address'] + '\t' + rental_data['title'] + '\n'print("Line " + str(link_num) + ": " + line)try:fw.writelines(line)except UnicodeEncodeError:passfw.flush()fw.close()return 0opts, args = getopt.getopt(sys.argv[1:], "ha:")
area = None
for op, value in opts:if op == "-h":print("Usage: python 爬虫.py -a area")print("Optimal areas are in file: areas.txt Or You can search them on www.xiaozhu.com")elif op == "-a":area = valueget_area_rental_information(area=area)else:print("ParameterError Usage: python 爬虫.py -a area")

四、代码详情(For Windows)

  该代码只对两种响应状态码进行处理,200代表网页信息被正常加载,202则表示IP被封,然后使用beautifulsoup对网页进行解析,提取我们所需要的信息。程序先对所给定区域的全部房屋租赁链接进行逐页面的爬取,在爬取到所有链接后,根据每条信息爬取对应的房屋租赁信息,每爬到一条信息就整合到到一个字典中,最后反序列化到一个自定的输出文件(filename)中,默认存储路径是F盘
  在命令行中直接输入“python .\爬虫_windows.py -a 区域”就开始爬取该地区的全部租赁信息,

输入“python .\爬虫_windows.py -h”查看帮助信息,

  该代码采用的反爬虫应对方法是当前线程随机等待一段时间(sys.sleep())再继续发送下一个请求,以此来模仿人的浏览方式,另外,该网站还会检查请求头中User-Agent的内容,requests中get方法默认的User-Agent是Python访问,因此这里对headers进行了替换,更多的反爬虫应对措施见下文。

五、爬虫代码(For Linux)

'''
@author: Ἥλιος
@CSDN:https://blog.csdn.net/qq_40793975/article/details/82734297
Platform:Windows Python3
'''
print(__doc__)from bs4 import BeautifulSoup
import requests
import re
import time
import random
import sys
import getopturl = 'http://sh.xiaozhu.com/'
proxies = {"http": "123.114.202.119:8118"}  # 代理IP
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0'
}   # 消息头
MunicList = ['sh', 'tj', 'cq', 'bj']    # 直辖市列表def get_page_links(url=None, page_links=None, label=0):"""爬取某个网页上的全部房屋租赁链接:param url: 网页链接:param page_links: 全部房屋租赁链接:param label: 标志位,该网页是第一页为1,否则为1:return: 状态码,0爬取成功,1IP被封导致爬取失败,2爬取成功且当前网页为最后一页"""sec = (random.random() + 0) * 10time.sleep(sec)  # 请求延时wb_data = requests.get(url, headers=header)if wb_data.status_code == 202:  # 页面响应状态202,IP被封print("IP blocked")return 1soup = BeautifulSoup(wb_data.text, 'lxml')links = soup.select('.pic_list > li > a:nth-of-type(1)')for link in links:page_link = link.get('href')page_links.append(page_link)info = soup.select('a.font_st')if len(info) <= 1 and label == 0:  # 判断当前页是不是最后一页,不检查第一页print("Last page")return 2return 0def detail2adress(str=None):"""使用正则表达式提取详细地址(非直辖市)中的省或行政区、市或自治区、区或县:param str: 详细地址:return: 省或行政区、市或自治区、区或县组成的列表"""result_default = [None, None, None]if str is None:return result_defaultresult = re.search('(?P<province>[^省]+省|[^行政区]+行政区)(?P<city>[^市]+市|[^自治区]+自治区)(?P<county>[^县]+县|[^区]+区)', str)if result is None:return result_defaultreturn list(result.groups())def detail2adress_Munic(str=None):"""使用正则表达式提取详细地址(直辖市)中的省或行政区、市或自治区、区或县:param str: 详细地址:return: 省或行政区、市或自治区、区或县组成的列表"""result_default = [None, None, None]if str is None:return result_defaultresult = re.search('(?P<city>[^市]+市)(?P<county>[^区]+区|[^县]+县)', str)if result is None:return result_defaultresult = list(result.groups())result_default[0] = result[0]result_default[1:3] = result[:]return result_defaultdef get_rental_information(url=None, Munic=0):"""根据链接爬取某个房屋租赁信息:param url: 待爬取房屋租赁信息的链接:param Munic: 标志位,1是直辖市,否则为0:return: 房屋租赁信息"""sec = (random.random() + 0) * 10time.sleep(sec)  # 请求延时wb_data = requests.get(url, headers=header)print(wb_data.status_code)if wb_data.status_code == 202:print("IP blocked")return 1soup = BeautifulSoup(wb_data.text, 'lxml')address = soup.select('.pho_info > p')[0].get('title')price = soup.select('.day_l > span:nth-of-type(1)')[0].textsize = soup.select('.border_none > p')[0].textnumber = soup.select('.h_ico2')[0].texttitle = soup.select('.pho_info > h4:nth-of-type(1) > em:nth-of-type(1)')[0].textpattern_size = re.compile(r'\d+')   # 查找数字pattern_number = re.compile(r'\d+')   # 查找数字size = pattern_size.findall(size.split(' ')[0])[0]number = pattern_number.findall(number)[0]data = {'address': detail2adress_Munic(address) if Munic else detail2adress(address),'price': int(price),'size': int(size),'number': int(number),'detail_address': address,'title': title}return datadef get_area_page_links(area=None):"""爬取某所有网页上的全部房屋租赁链接:param area: 这些网页所属的地区:return: 全部房屋租赁链接"""sec = (random.random() + 1) * 10time.sleep(sec)page_links = []for i in range(100):label = 0if i + 1 == 1:label = 1url = 'http://{}.xiaozhu.com/'.format(area)else:url = 'http://{}.xiaozhu.com/search-duanzufang-p{}-0/'.format(area, i + 1)res = get_page_links(url, page_links, label)print("Area: " + area + " ,Page: " + str(i+1))print(len(page_links))if res != 0:breakreturn page_linksdef get_area_rental_information(area=None, path=None):"""根据该地区的全部房屋租赁链接爬取房屋租赁信息:param area: 这些房屋租赁链接所属的地区:return: 状态码,0爬取成功, 1IP被封导致爬取失败"""Munic = 0if area in MunicList:Munic = 1area_page_links = get_area_page_links(area)filename = path + area + '_rental_information.txt'  # 租赁信息存储路径try:fw = open(filename, 'w', encoding='utf-8')except IOError:print("Fail in open file" + filename)else:link_num = 0for page_link in area_page_links:link_num += 1rental_data = get_rental_information(page_link, Munic)failed_time = 1while rental_data == 1 and failed_time <= 3:  # 失败重试sys.wait(10000)print("Retry " + failed_time + " time!")rental_data = get_rental_information(page_link, Munic)failed_time += 1if rental_data == 1:print("Retry Failed!")raise Exception("Crawling Failed!Next Area")return 1try:line = rental_data['address'][0] + '\t' + rental_data['address'][1] \+ '\t' + rental_data['address'][2] + '\t' + str(rental_data['price'])\+ '\t' + str(rental_data['size']) + '\t' + str(rental_data['number']) + '\t' \+ rental_data['detail_address'] + '\t' + rental_data['title'] + '\n'except TypeError:print("Error in URL: " + page_link)continueelse:print("Line " + str(link_num) + ": " + line)try:fw.writelines(line)except UnicodeEncodeError:passfw.flush()fw.close()return 0opts, args = getopt.getopt(sys.argv[1:], "ha:p:")
area = None
path = None
opt_num = len(opts)
opt_id = 0
for op, value in opts:opt_id += 1if op == "-h" and opt_num == 1 and value == None:print("Usage: python pachong.py -a area -p path")print("Optimal areas are in file: areas.txt Or You can search them on www.xiaozhu.com")elif op == "-a" and value != None and (opt_num == 1 or opt_num == 2):area = valueif opt_num == 1:get_area_rental_information(area=area, path='//root//simple_log_analysis//srcdata//')else:if opt_id == opt_num:get_area_rental_information(area=area, path=path)elif op == "-p" and value != None and (opt_num == 1 or opt_num == 2):path = valueif opt_num == 1:get_area_rental_informationn(area='sh', path=path)else:if opt_id == opt_num:                                   get_area_rental_information(area=area, path=path)else:print("ParameterError Usage: python pachong.py -a area -p path or -h for Help")

六、代码详情(For Linux)

  代码总体相较于Windows版本没有什么较大的改动,在爬取过程中遇到IP被封的情况,该代码会重试三次,这样就有30秒的时间重新恢复访问(在页面上输入验证码,解封后建议不要关闭该页面,下次IP再被封后可以直接刷新该页面),而不需要重新从头爬取,如下图所示,

另外,多加了一个命令行参数-p,来指代爬取到的信息汇总文件的存储位置,因此命令行的调用方式有:
1. “python3 .\爬虫_linux.py -a 区域 -p 存储路径”
2. “python3 .\爬虫_linux.py -a 区域”(默认路径/root/simple_log_analysis/srcdata/)
3. “python3 .\爬虫_linux.py -p 存储路径”(默认爬取区域sh)
4. “python3 .\爬虫_linux.py -h”(帮助信息)

七、后续改进

  该爬虫程序的主要问题:爬取效率太低、对其他错误状态码没有做处理,对于“爬取效率低”的问题,解决方法有:
1. 使用IP池,IP被封后可以立即使用代理IP进行爬取;
2. 使用多线程爬虫框架Scrapy和IP池结合,多线程爬取提高爬取效率;
3. 几次访问后就修改headers,造成不同用户访问的假象(网上提供的方法,不知道实际可行与否);
4. 对于JS渲染可以寻找JS的API接口,进一步解析;
5. 对于异步加载方式,可以切换浏览器的“响应设计模式”为手机访问。
  这篇代码为什么没有采用上述方法呢?代理IP池太贵,而且不稳定,即使使用Scrapy,如果单靠一个IP去访问,很容易被封IP。

爬虫就介绍到这里,下一篇一起来写一个Linux的shell脚本吧。有环境问题或者其他问题可以在下方评论区提问偶,我看到的话会进行回复。

自己动手搭建一个简单的基于Hadoop的离线分析系统之一——网络爬虫相关推荐

  1. 自己动手搭建一个简单的静态资源服务器

    文章目录 自己动手搭建一个简单的静态资源服务器 介绍 一.设计到的模块 备注: 二.代码结构 三.具体代码 自己动手搭建一个简单的静态资源服务器 介绍 ​ 通过此文章的学习,可以自己使用NodeJs搭 ...

  2. 自己动手搭建一个简单的网站

    我准备搭建一个属于自己的网站,一方面是了解建站的知识,另一个方面是为了测试Http请求相关的内容. 建站资料 下面是建站需要的资料: 服务器:也就是高级一点的电脑,它主要用来存放网页数据: web服务 ...

  3. Jenkins——如何快速搭建一个简单的基于 Jenkins 的持续集成环境

    首先,在Jenkins官网(http://jenkins-ci.org/)上,下载所需要用的Jenkins版本.我们都是Windows系统,下载Windows版的即可.下载下来之后,进行安装,一步一步 ...

  4. flask post json_Flask 和 requests 搭建一个简单的API服务

    (点击上方快速关注并设置为星标,一起学Python) 路由器为腾达路由器,使用requests来进行数据的获取,使用flask来进行实现api的搭建 requests我就不介绍了,这个大家都很熟悉了, ...

  5. 动手造轮子:实现一个简单的基于 Console 的日志输出

    动手造轮子:实现一个简单的基于 Console 的日志输出 Intro 之前结合了微软的 Logging 框架和 Serilog 写了一个简单的日志框架,但是之前的用法都是基于 log4net.ser ...

  6. 我的Serverless实战—基于Serverless搭建一个简单的WordPress个人博客图文详解-JJZ

    文正在参与 "100%有奖 | 我的Serverless 实战"征稿活动 活动链接:https://marketing.csdn.net/p/15940c87f66c68188cf ...

  7. 怎样用python搭建简单的系统_如何用Python搭建一个简单的推荐系统?

    推荐系统的相关知识我们已在前文中提到,在这篇文章中,我们会介绍如何用Python来搭建一个简单的推荐系统. 本文使用的数据集是MovieLens数据集,该数据集由明尼苏达大学的Grouplens研究小 ...

  8. python旅游推荐系统_如何用Python搭建一个简单的推荐系统?

    推荐系统的相关知识我们已在前文中提到,在这篇文章中,我们会介绍如何用Python来搭建一个简单的推荐系统. 本文使用的数据集是MovieLens数据集,该数据集由明尼苏达大学的Grouplens研究小 ...

  9. 通过Dapr实现一个简单的基于.net的微服务电商系统(十一)——一步一步教你如何撸Dapr之自动扩/缩容...

    上一篇我们讲到了dapr提供的bindings,通过绑定可以让我们的程序轻装上阵,在极端情况下几乎不需要集成任何sdk,仅需要通过httpclient+text.json即可完成对外部组件的调用,这样 ...

  10. 通过Dapr实现一个简单的基于.net的微服务电商系统(四)——一步一步教你如何撸Dapr之订阅发布...

    之前的章节我们介绍了如何通过dapr发起一个服务调用,相信看过前几章的小伙伴已经对dapr有一个基本的了解了,今天我们来聊一聊dapr的另外一个功能--订阅发布 目录: 一.通过Dapr实现一个简单的 ...

最新文章

  1. java 异常的分类并举例_Java异常处理中的一些特殊情况举例
  2. ISME Comm:南农韦中等-菌群移植筑建根际免疫新防线
  3. java 反射 速度_Java反射获取实例的速度对比分析
  4. 关于SEL的简单总结
  5. Servlet、Struts2以及SpringMvc中的线程安全
  6. 手写一个动态代理实现,手写Proxy,手写ClassLoader,手写InvocationHandler
  7. 08.实例方法和类方法的区别与及工厂方法
  8. 关注程序员健康之——研究显示白天小睡90分钟将有效增强记忆力
  9. linux内核双向链表学习
  10. python多线程执行_一个Python多线程运行实例
  11. hbuildx打包成apk_基于HBuilder将H5站点打包成app
  12. FFmpeg源码分析:音频滤镜介绍(下)
  13. 层次分析法步骤及源代码
  14. AI安全技术总结与展望
  15. java公路中轴_技术帖 自行车中轴的种类 入门车友请细读
  16. 文本预处理:拼写纠错
  17. 哪个牌子的运动耳机比较好、运动蓝牙耳机排行榜
  18. Win10 自定义右键新建菜单
  19. 菲尔人格测试今天你测了吗?
  20. Excel多个工作表合并,如何去除每个工作表中的表头,只保留一个表头

热门文章

  1. 专业图形卡测试软件,专业卡能玩游戏么?实测很是意外
  2. 【资源】DNW驱动,Win7 64位可用
  3. Spring DI和AOP简介(一)
  4. 中国特殊灯具行业市场供需与战略研究报告
  5. EMCP  物联网云平台萤石云密钥功能使用手册
  6. 从一道面试题掌握ES6的综合运用(有彩蛋)
  7. java解析.eml文件_PHP读取、解析eml文件及生成网页的方法示例
  8. 【离散数学】单射、满射与双射
  9. HTML+CSS项目练习(8)-发光文字动画
  10. 为什么Wannacry 勒索病毒加密的部分数据能恢复?