1. 程式人生 > >自己動手搭建一個簡單的基於Hadoop的離線分析系統之一——網路爬蟲

自己動手搭建一個簡單的基於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

二、待爬資訊

  我選擇的房屋租賃資訊網站是小豬短租

,該網站沒有使用大量的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 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