自己動手搭建一個簡單的基於Hadoop的離線分析系統之一——網路爬蟲
自己動手搭建一個簡單的基於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
二、待爬資訊
我選擇的房屋租賃資訊網站是小豬短租
待爬資訊有:出租房屋所在省、市、區,起步價格,房屋面積,適宜居住的人數,出租標題資訊,詳細地址,如下圖所示。
三、爬蟲程式碼(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 getopt
url = '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) * 10
time.sleep(sec) # 請求延時
wb_data = requests.get(url, headers=header)
if wb_data.status_code == 202: # 頁面響應狀態202,IP被封
print("IP blocked")
return 1
soup = 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 2
return 0
def detail2adress(str=None):
"""
使用正則表示式提取詳細地址(非直轄市)中的省或行政區、市或自治區、區或縣
:param str: 詳細地址
:return: 省或行政區、市或自治區、區或縣組成的列表
"""
result_default = [None, None, None]
if str is None:
return result_default
result = re.search('(?P<province>[^省]+省|[^行政區]+行政區)(?P<city>[^市]+市|[^自治區]+自治區)(?P<county>[^縣]+縣|[^區]+區)', str)
if result is None:
return result_default
return list(result.groups())
def detail2adress_Munic(str=None):
"""
使用正則表示式提取詳細地址(直轄市)中的省或行政區、市或自治區、區或縣
:param str: 詳細地址
:return: 省或行政區、市或自治區、區或縣組成的列表
"""
result_default = [None, None, None]
if str is None:
return result_default
result = re.search('(?P<city>[^市]+市)(?P<county>[^區]+區)', str)
if result is None:
return result_default
result = list(result.groups())
result_default[0] = result[0]
result_default[1:3] = result[:]
return result_default
def get_rental_information(url=None, Munic=0):
"""
根據連結爬取某個房屋租賃資訊
:param url: 待爬取房屋租賃資訊的連結
:param Munic: 標誌位,1是直轄市,否則為0
:return: 房屋租賃資訊
"""
sec = (random.random() + 0) * 10
time.sleep(sec) # 請求延時
wb_data = requests.get(url, headers=header)
if wb_data.status_code == 202:
print("IP blocked")
return 1
soup = 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].text
size = soup.select('.border_none > p')[0].text
number = soup.select('.h_ico2')[0].text
title = soup.select('.pho_info > h4:nth-of-type(1) > em:nth-of-type(1)')[0].text
pattern_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 data
def get_area_page_links(area=None):
"""
爬取某所有網頁上的全部房屋租賃連結
:param area: 這些網頁所屬的地區
:return: 全部房屋租賃連結
"""
sec = (random.random() + 1) * 10
time.sleep(sec)
page_links = []
for i in range(100):
label = 0
if i + 1 == 1:
label = 1
url = '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:
break
return page_links
def get_area_rental_information(area=None):
"""
根據該地區的全部房屋租賃連結爬取房屋租賃資訊
:param area: 這些房屋租賃連結所屬的地區
:return: 狀態碼,0爬取成功, 1IP被封導致爬取失敗
"""
Munic = 0
if area in MunicList:
Munic = 1
area_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 = 0
for page_link in area_page_links:
link_num += 1
rental_data = get_rental_information(page_link, Munic)
if rental_data == 1:
fw.flush()
fw.close()
return 1
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'
print("Line " + str(link_num) + ": " + line)
try:
fw.writelines(line)
except UnicodeEncodeError:
pass
fw.flush()
fw.close()
return 0
opts, 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 = value
get_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 getopt
url = '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) * 10
time.sleep(sec) # 請求延時
wb_data = requests.get(url, headers=header)
if wb_data.status_code == 202: # 頁面響應狀態202,IP被封
print("IP blocked")
return 1
soup = 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 2
return 0
def detail2adress(str=None):
"""
使用正則表示式提取詳細地址(非直轄市)中的省或行政區、市或自治區、區或縣
:param str: 詳細地址
:return: 省或行政區、市或自治區、區或縣組成的列表
"""
result_default = [None, None, None]
if str is None:
return result_default
result = re.search('(?P<province>[^省]+省|[^行政區]+行政區)(?P<city>[^市]+市|[^自治區]+自治區)(?P<county>[^縣]+縣|[^區]+區)', str)
if result is None:
return result_default
return list(result.groups())
def detail2adress_Munic(str=None):
"""
使用正則表示式提取詳細地址(直轄市)中的省或行政區、市或自治區、區或縣
:param str: 詳細地址
:return: 省或行政區、市或自治區、區或縣組成的列表
"""
result_default = [None, None, None]
if str is None:
return result_default
result = re.search('(?P<city>[^市]+市)(?P<county>[^區]+區|[^縣]+縣)', str)
if result is None:
return result_default
result = list(result.groups())
result_default[0] = result[0]
result_default[1:3] = result[:]
return result_default
def get_rental_information(url=None, Munic=0):
"""
根據連結爬取某個房屋租賃資訊
:param url: 待爬取房屋租賃資訊的連結
:param Munic: 標誌位,1是直轄市,否則為0
:return: 房屋租賃資訊
"""
sec = (random.random() + 0) * 10
time.sleep(sec) # 請求延時
wb_data = requests.get(url, headers=header)
print(wb_data.status_code)
if wb_data.status_code == 202:
print("IP blocked")
return 1
soup = 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].text
size = soup.select('.border_none > p')[0].text
number = soup.select('.h_ico2')[0].text
title = soup.select('.pho_info > h4:nth-of-type(1) > em:nth-of-type(1)')[0].text
pattern_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 data
def get_area_page_links(area=None):
"""
爬取某所有網頁上的全部房屋租賃連結
:param area: 這些網頁所屬的地區
:return: 全部房屋租賃連結
"""
sec = (random.random() + 1) * 10
time.sleep(sec)
page_links = []
for i in range(100):
label = 0
if i + 1 == 1:
label = 1
url = '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:
break
return page_links
def get_area_rental_information(area=None, path=None):
"""
根據該地區的全部房屋租賃連結爬取房屋租賃資訊
:param area: 這些房屋租賃連結所屬的地區
:return: 狀態碼,0爬取成功, 1IP被封導致爬取失敗
"""
Munic = 0
if area in MunicList:
Munic = 1
area_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 = 0
for page_link in area_page_links:
link_num += 1
rental_data = get_rental_information(page_link, Munic)
failed_time = 1
while 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 += 1
if rental_data == 1:
print("Retry Failed!")
raise Exception("Crawling Failed!Next Area")
return 1
try:
line = rental_data['address'][0] + '\t' + rental_data['add