從零開始學爬蟲(3):通過MongoDB資料庫獲取爬蟲資料

weixin_33936401發表於2017-02-23

這是我學習爬蟲的筆記,作為備忘,如果可以幫到大家,那就更好了~
從零開始學爬蟲(1):爬取房天下二手房資訊
從零開始學爬蟲(2):突破限制,分類爬取,獲得全部資料
從零開始學爬蟲(3):通過MongoDB資料庫獲取爬蟲資料

一、MongoDB資料庫是什麼

MongoDB是一個基於分散式檔案儲存的資料庫。他支援的資料結構非常鬆散,是類似json的bson格式,因此可以儲存比較複雜的資料型別。Mongo最大的特點是他支援的查詢語言非常強大,其語法有點類似於物件導向的查詢語言,幾乎可以實現類似關聯式資料庫單表查詢的絕大部分功能,而且還支援對資料建立索引。
  一個MongoDB 例項可以包含一組資料庫,一個DataBase 可以包含一組Collection(集合),一個集合可以包含一組Document(文件)。一個Document包含一組field(欄位),每一個欄位都是一個key/value pair。

以上是百度百科的介紹,那麼所謂的bson格式是什麼東西呢?文件是什麼樣的?集合又是什麼呢?下圖是我在mongodb視覺化管理工具上Robomongo截的圖:

1623609-c3fbacecd79f0c1a.png
圖片.png

好了,那我們可以看到一個集合裡面有很多個文件,這類似於一個關係型資料庫一個表裡面有多條記錄。

二、MongoDB安裝與配置

安裝MongoDB資料庫

MongoDB資料庫安裝的詳細過程參見 Windows下安裝MongoDB
安裝MongoDB資料庫視覺化管理工具Robomongo:直接去官網下載安裝即可,下載地址:https://robomongo.org/download

安裝Pymongo

以管理員身份開啟命令列提示符,執行pip3 install pymongo,即可完成安裝。

至此MongoDB資料庫的安裝與配置就已經完成了。

三、如何通過pymongo操作MongoDB

1、連線資料庫

第一步,通過pymongo建立資料庫連線,並且連線一個名為test的資料庫(如果test不存在,這一步就是新建一個test資料庫)

from pymongo import MongoClient
# 建立 MongoDB 資料庫連線
conn = MongoClient('localhost', 27017)
# 新建一個test資料庫
db = conn.test
2、資料庫操作

假設向test資料庫中的test集合中插入文件data(data = {"name": "xyl", "age": 25}),只需要一條語句db.test.insert_one(data)就行了;多個文件的插入用db.test.insert_many(data)。

db = conn.test

# 插入一條記錄
data = {"name": "xyl", "age": 25}
db.test.insert_one(data)

# 插入多條記錄
data = [{"name": "xyl", "age": 25}, {"name": "yxl", "age": 27}]
db.test.insert_many(data)

# 遍歷coll = db.test.find()查詢到的所有結果,以及它key=name的value
coll = db.test.find()
for i in col1:
    print(i)
    print(i["name"])

OK,其實MongoDB也是一個資料庫,也是可以通過一些語句對其中的資料進行各種操作,這裡我們先不深究,有興趣的可以看一下官方文件。現在,我們來把上一篇文章最後的問題解決一下。

3、二手房資訊儲存與匯出

基於pymongo的常用操作,我們來改寫一下上篇文章中的程式碼(以下是修改後的資料儲存的程式碼)

# -*- coding: utf-8 -*-
from pymongo import MongoClient
from bs4 import BeautifulSoup
import requests
import time

# 建立 MongoDB 資料庫連線
conn = MongoClient('localhost', 27017)

# 新建一個test資料庫
db = conn.test

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
    'Cookie': 'global_cookie=gpe9gqaciutsnx2zke6bsyizp29iypii6li; polling_imei=231c34cb02f4f783; SoufunSessionID_Esf=3_1486111884_10590; SKHRecordsXIAN=%25e5%258d%258e%25e8%25bf%259c%25e6%25b5%25b7%25e8%2593%259d%25e5%259f%258e%257c%255e2017%252f2%252f13%2b16%253a13%253a18%257c%255e3610937652%257c%2523%25e6%259d%258e%25e5%25ae%25b6%25e6%259d%2591%25e4%25b8%2587%25e8%25be%25be%25e5%25b9%25bf%25e5%259c%25ba%25e9%259b%2581%25e5%25a1%2594%25e8%25b7%25af%25e6%2596%2587%25e8%2589%25ba%25e8%25b7%25af%25e5%258f%258b%25e8%25b0%258a%25e8%25b7%25af%25e9%2593%2581%25e4%25b8%2580%25e4%25b8%25ad215%25e5%25b9%25b3%25e5%2586%2599%25e5%25ad%2597%25e9%2597%25b4%257c%255e2017%252f2%252f16%2b21%253a41%253a27%257c%255e0; logGuid=1d724a5a-3c98-4e52-8e84-924a7ca9a1ac; __utmt_t0=1; __utmt_t1=1; __utmt_t2=1; city=xian; __utma=147393320.1839238857.1486108289.1487256095.1487298760.22; __utmb=147393320.87.10.1487298760; __utmc=147393320; __utmz=147393320.1486191809.5.3.utmcsr=esf.xian.fang.com|utmccn=(referral)|utmcmd=referral|utmcct=/; unique_cookie=U_6qx5dbebzr1btgohc0anj3f2p16iz97a455*29'
}

def spider_1(url):
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, 'lxml')

    titles = soup.select('dd > p.title > a')            # 標題
    hrefs = soup.select('dd > p.title > a')            # 連結
    details = soup.select('dd > p.mt12')                # 建築資訊
    courts = soup.select('dd > p:nth-of-type(3) > a')   # 小區
    adds = soup.select('dd > p:nth-of-type(3) > span')  # 地址
    areas = soup.select('dd > div.area.alignR > p:nth-of-type(1)')     # 面積
    prices = soup.select('dd > div.moreInfo > p:nth-of-type(1) > span.price')  # 總價
    danjias = soup.select('dd > div.moreInfo > p.danjia.alignR.mt5')    # 單價
    authors = soup.select('dd > p.gray6.mt10 > a')      # 釋出者
    trains = soup.select('dd > div.mt8.clearfix > div.pt4.floatl > span.train.note')   # 地鐵房

    for title, href, detail, court, add, area, price, danjia, author, train in zip(titles, hrefs, details, courts, adds, areas, prices, danjias, authors, trains):
        data = {
            'title': title.get_text(),
            'href': 'http://esf.xian.fang.com' + href.get('href'),
            'detail': list(detail.stripped_strings),
            'court': court.get_text(),
            'add': add.get_text(),
            'area': area.get_text(),
            'price': price.get_text(),
            'danjia': danjia.get_text(),
            'author': author.get_text(),
            'train': train.get_text()
        }
        db.test.insert_one(data)

response = requests.get('http://esf.xian.fang.com/', headers=headers)
soup = BeautifulSoup(response.text, 'lxml')

regions = soup.select('#list_D02_10 > div.qxName > a')  # 區域
totprices = soup.select('#list_D02_11 > p > a')  # 總價
housetypes = soup.select('#list_D02_12 > a')  # 戶型
areas = soup.select('#list_D02_13 > p > a')  # 面積

# 對於大於等於100頁的進行細分
n = 1
while n < 8:
    i = 1
    while i < 7:
        resp1 = requests.get('http://esf.xian.fang.com' + regions[n].get('href') + '-' + housetypes[i].get('href')[7:], headers=headers)
        soup1 = BeautifulSoup(resp1.text, 'lxml')
        pages = soup1.select('#list_D10_01 > span > span')  # 頁數
        if pages and pages[0].get_text().split('/')[1] == '100':
            m = 1
            while m < 10:
                resp1 = requests.get('http://esf.xian.fang.com' + regions[n].get('href') + totprices[m].get('href')[7:-1] + '-' + housetypes[i].get('href')[7:], headers=headers)
                soup1 = BeautifulSoup(resp1.text, 'lxml')
                pages = soup1.select('#list_D10_01 > span > span')  # 頁數
                if pages and pages[0].get_text().split('/')[1] == '100':
                    j = 1
                    while j < 10:
                        resp1 = requests.get('http://esf.xian.fang.com' + regions[n].get('href') + totprices[m].get('href')[7:-1] + '-' + housetypes[i].get('href')[7:-1] + '-' + areas[j].get('href')[7:], headers=headers)
                        soup1 = BeautifulSoup(resp1.text, 'lxml')
                        pages = soup1.select('#list_D10_01 > span > span')  # 頁數
                        if pages:
                            k = int(pages[0].get_text().split('/')[1])
                            while k > 0:
                                spider_1('http://esf.xian.fang.com' + regions[n].get('href') + totprices[m].get('href')[7:-1] + '-' + housetypes[i].get('href')[7:-1] + '-' + areas[j].get('href')[7:-1] + '-i3' + str(k))
                                k = k - 1
                                time.sleep(2)
                        else:
                            pass
                        j = j + 1
                else:
                    if pages:
                        k = int(pages[0].get_text().split('/')[1])
                        while k > 0:
                            spider_1('http://esf.xian.fang.com' + regions[n].get('href') + totprices[m].get('href')[7:-1] + '-' + housetypes[i].get('href')[7:-1] + '-i3' + str(k))
                            k = k - 1
                            time.sleep(2)
                    else:
                        pass
                m = m + 1
        else:
            if pages:
                k = int(pages[0].get_text().split('/')[1])
                while k > 0:
                    spider_1('http://esf.xian.fang.com' + regions[n].get('href') + housetypes[i].get('href')[7:-1] + '-i3' + str(k))
                    k = k - 1
                    time.sleep(2)
            else:
                pass
        i = i + 1
    n = n + 3         # 只取1/4/7

這段程式碼執行的時候我們就可以開啟Robomongo去檢視當前爬取下來的資料量,它支援三種模式的檢視:


1623609-abeb3070abfd13d5.png
圖片.png

1623609-c22aa75e55b4f71a.png
圖片.png

1623609-9f096b8cc8dba87c.png
圖片.png

  OK,沒問題的話就讓他一直執行下去,直至把我們需要的資料全部爬下來。雖然MongoDB是一種檔案型資料庫,但它畢竟不是資料分析工具,接下來,我們需要把這些資料匯出為txt、csv、excel等檔案型別,方便後續處理。
  重寫一段專門用來匯出已經儲存在test資料庫test集合中的資料如下:

# -*- coding: utf-8 -*-
from pymongo import MongoClient
# 建立 MongoDB 資料庫連線
conn = MongoClient('localhost', 27017)
# 新建一個test資料庫
db = conn.test
Myfile = open('D:\ershoufang.txt', 'w', encoding='utf-8')
coll = db.test.find()
k = 1
for i in coll:
# 把欄位名寫入檔案第一行
    while k == 1:
        m = 1
        for j in i:
            if m < 11:
                Myfile.write(j + "\t")
            else:
                Myfile.write(j + "\n")
            m = m + 1
        k = k + 1
# 把欄位值寫入檔案第2到x行
    n = 1
    for j in i:
        if n < 11:
            Myfile.write(str(i[j]) + "\t")
        else:
            Myfile.write(str(i[j]) + "\n")
        n = n + 1

注意,在windows下面,新檔案的預設編碼是gbk,這樣的話,python直譯器會用gbk編碼去解析我們的網路資料流txt,然而txt此時已經是decode過的unicode編碼,這樣的話就會導致解析不了,出現上述問題。 解決的辦法就是,改變目標檔案的編碼:Myfile = open('D:\ershoufang.txt', 'w', encoding='utf-8')中加上encoding='utf-8'。

匯出以tab分隔的txt資料:

1623609-ecb766fe718e894b.png
圖片.png

四、後記

到現在為止,簡單的網頁資訊爬取和資料儲存基本上可以搞定了,剩下的就是資料的整理和分析。
  當然,其實在爬取網頁資訊的過程中我們還遇到一些問題:同一個網頁每次爬取到的文件數不同,據瞭解,應該是因為目標網站有反爬蟲策略(ban),對方可以根據你的訪問頻率和IP判斷你是爬蟲,而不是正常的瀏覽。知乎大神介紹了3個常用的防ban的策略:

1、設定隨機漫步。訪問間隔時間可以設為一個服從正態分佈的隨機數,模擬人類瀏覽網頁的頻率。
2、設定爬蟲的工作週期。深更半夜就停止工作,早上就開始工作,儘量模擬人類的作息。
3、搭建叢集,分散式爬蟲。這樣訪問的IP會不固定,對方不容易判斷出你是爬蟲。這個開發成本就相對較高了。推薦NUTCH框架。
作者:淡泊明志
連結:https://www.zhihu.com/question/38123412/answer/75123814
來源:知乎

我們們有空再來研究研究~~~待續

相關文章