爬蟲(4)--- 一起來爬廣東高校快遞信息


目錄
1.“看不見”的數據
2. 讓數據現身
  2.1 數據API分析
    2.1.1 數據文件在哪里?
    2.1.2 guid(數據全局唯一標識符)在哪里?
    2.2.3 如何獲取guid?
3. 爬蟲構建
  3.1 搭建爬蟲環境
  3.2 瀏覽器偽裝
  3.3 構建代理IP池
  3.4 廣東高校數據獲取
  3.5 數據獲取與解析
  3.6 數據存儲
  3.7 主函數
4. 結果與可視化
  4.1 結果表結構
  4.2 數據可視化


1.“看不見”的數據

反爬機制層出不窮,其中很重要的一種手法是對數據進行隱藏,即在源碼中看不到實際的數據,主要手段如下:

  • 將數據存儲于json/XHR等文件中,如拉勾網、京東商城價格信息等;
  • 直接隱藏數據,需要點擊才能查看完整數據,如快遞100網等
  • ......

2.讓數據現身

  本章節以爬取快遞100網快遞員手機號碼、公司與歸屬地等信息為例。

2.1 數據API分析

快遞100是一個集快遞單號查詢、快遞單號短信跟蹤、快遞網點查詢、網上寄快遞等為一體的綜合性快遞物流服務網站。其上面的某些較為敏感信息,如快遞員手機號碼的反扒機制是采用隱藏信息的方式,如搜索金蝶大廈附近的快遞員,結果如下圖:

反扒機制

用戶只有通過按鈕“點擊查看完整號碼”才能看到完整號碼,即使點擊查看完整號碼之后在源碼中仍不能看到號碼所在,很明顯其數據已經被隱藏,我們可以猜測其數據可能被存儲于XHR或者js文件中,接下來我們的目的便在研究數據到底通過哪種方式隱藏起來?我們該怎么獲???

2.1.1 數據文件在哪里?

(1)啟動Chrome瀏覽器
打開快遞100搜索頁面,并按F12打開開發環境;

(2)如下圖選定XHR

(3)點擊按鈕“點擊查看完整號碼”

我們可以看到其產生了一個XHR文件,這個很可能便是數據所在。打開該文件,如下圖,這果然便是我們數據所在。


  可以看到數據的URL如下,構造該XHR的關鍵就在于后面的guid值(唯一標識符)。所以問題便轉換為如何找到每個搜索關鍵詞的guid?

https://www.kuaidi100.com/courier/searchapi.do?method=courierdetail&json={%22guid%22:%22399F251E9C6C464FCDC93A73C93BD391%22}

2.1.2 guid(數據全局唯一標識符)在哪里?

在尋找guid之前,我們先看一下該網站的用戶行為觸發操作,即當用戶點擊“查看完整號碼”時候網頁將產生什么?

(1)查看網頁源碼

我們點擊“查看完整號碼”之后,右擊查看源碼并在源碼中搜索該號碼,納尼,沒能找到號碼,就連收件地址也找不到。很明顯,網頁是將相關信息給隱藏起來了。

在此必須明白所謂源碼,就是網站服務器發送到瀏覽器的原封不動的代碼,上面我們又知道每次點擊“查看完整號碼”的時候會產生一個XHR文件并且頁面的號碼也全部顯示出來,瀏覽器執行js動態肯定會產生新的HTML代碼,我們在源碼中找不到的代碼,但通過審查元素便能看到最終的HTML代碼。我們接下來我們通過審查元素進行分析。

(2)檢查元素

將鼠標移至“點擊查看完整號碼”上面,點擊右鍵檢查。


至此,我們便找到了guid。接下來我們將考慮如何用程序模擬用戶點擊按鈕操作,并獲取快遞員相關公開信息。

2.1.3 如何獲取guid?

顯然通過HTTP請求獲取源碼解析是不可行的,在此采用模擬瀏覽器,Python中的selenium就是一種使用瀏覽器內核去訪問鏈接,跟真正的瀏覽器訪問頁面沒有什么差別,并且我們可以通過解析得到guid,至于selenium的環境配置在此就不詳細講解了,具體可搜索解決。

實際上便是模擬瀏覽行為:打開瀏覽器-->輸入快遞100網網址-->輸入要搜索地區-->點擊按鈕“點擊查看完整號碼”。

除此之外如要獲取不同地區(如全廣州市)的快遞員信息,還需地理關鍵詞信息。為此,在網上找到了一份全國的省市區鎮街道四級聯動地理位置數據,入庫后其數據表格式如下(在后面只提供了相關接口調用,后續有需要再進行爬取):

地理位置數據示例

3. 爬蟲構建

3.1 搭建爬蟲環境

  • Windows 8.1
  • Python 3.5

導入Python第三方庫。

# 1、HTTP請求
import urllib.request  #請求
import urllib.parse  #URL編碼
import time  #設置延時
from multiprocessing.dummy import Pool  #多線程
import random
# 2、模擬瀏覽器
from selenium import webdriver
from selenium.webdriver.common.by import By
# 3、數據解析
import json  #json格式解析
from lxml import etree  #解析為XML和HTML
import re  #正則匹配
# 4、數據存儲
import MySQLdb

3.2 瀏覽器偽裝

def getUserAgent():
    '''
    功能:隨機獲取HTTP_User_Agent
    '''
    user_agents=[
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
    "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
    "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
    "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10"
    ]
    user_agent = random.choice(user_agents)
    return user_agent

3.3 構建代理IP池

def getProxies(pages):
    '''
    功能:爬取西刺高匿IP構造原始代理IP池
    @pages:獲取多少頁原始代理IP
    '''
    init_proxies = []
    ##爬取前十頁
    for i in range(1,pages+1):
        print("####")
        print("####爬取第"+str(i)+"頁####")
        print("####")        
        print("IP地址\t\t\t端口\t存活時間\t\t驗證時間")
        url = "http://www.xicidaili.com/nn/"+str(i)
        user_agent = getUserAgent()
        headers=("User-Agent",user_agent)
        opener = urllib.request.build_opener() 
        opener.addheaders = [headers] 
        try:
            data = opener.open(url,timeout=5).read()
        except Exception as er:
            print("爬取的時候發生錯誤,具體如下:")
            print(er)
        selector=etree.HTML(data) 
        ip_addrs = selector.xpath('//tr[@class="odd"]/td[2]/text()')  #IP地址
        port = selector.xpath('//tr[@class="odd"]/td[3]/text()')  #端口
        sur_time = selector.xpath('//tr[@class="odd"]/td[9]/text()')  #存活時間
        ver_time = selector.xpath('//tr[@class="odd"]/td[10]/text()')  #驗證時間
        for j in range(len(ip_addrs)):
            ip = ip_addrs[j]+":"+port[j] 
            init_proxies.append(ip)
            print(ip_addrs[j]+"\t\t"+port[j]+"\t\t"+sur_time[j]+"\t"+ver_time[j])#輸出爬取數據 
    return init_proxies

    
def testProxy(curr_ip):
    '''
    功能:驗證IP有效性
    @curr_ip:當前被驗證的IP
    '''
    tmp_proxies = []
    tarURL = "http://www.baidu.com/" 
    user_agent = getUserAgent()
    proxy_support = urllib.request.ProxyHandler({"http":curr_ip})
    opener = urllib.request.build_opener(proxy_support)
    opener.addheaders=[("User-Agent",user_agent)]
    urllib.request.install_opener(opener)
    try:
        res = urllib.request.urlopen(tarURL,timeout=5).read()
        if len(res)!=0:
            tmp_proxies.append(curr_ip)
    except urllib.error.URLError as er2: 
        if hasattr(er2,"code"):
            print("驗證代理IP("+curr_ip+")時發生錯誤(錯誤代碼):"+str(er2.code))
        if hasattr(er2,"reason"):
            print("驗證代理IP("+curr_ip+")時發生錯誤(錯誤原因):"+str(er2.reason))
    except Exception as er:
        print("驗證代理IP("+curr_ip+")時發生如下錯誤):")
        print(er)
    return tmp_proxies
        
##2.3 多線程驗證     
def mulTestProxies(unchecked_proxies):
    '''
    功能:多線程驗證IP有效性
    @tmp_proxies:原始代理IP池
    '''
    pool = Pool(processes=3)
    fl_proxies = pool.map(testProxy,unchecked_proxies)
    pool.close()
    pool.join()  #等待進程池中的worker進程執行完畢
    return fl_proxies

3.4 廣東高校數據獲取

中國高校之窗上的數據為例進行爬取與解析,該網站的HTML文件不是完全規范化與標準化,所以解析要注意下,在此就不一一解釋了,直接看代碼。

def getSchoolInfo():
    '''
    功能:獲取廣東高校信息
    '''
    url = "http://www.gx211.com/gxmd/gx-gd.html"
    user_agent = getUserAgent()
    headers=("User-Agent",user_agent)
    opener = urllib.request.build_opener() 
    opener.addheaders = [headers] 
    try:
        data = opener.open(url,timeout=5).read()
    except Exception as er:
        print("爬取的時候發生錯誤,具體如下:")
        print(er)
    ####解析數據(非規整的html文件)
    selector = etree.HTML(data)
    school_name_list1 = selector.xpath('//div[@id!="Div0"]/table/tbody/tr/td[1]')
    school_name_list2 = selector.xpath('//div[@id="Div3"]/table/tr/td[1]')
        
    school_zhuguan_list1 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tbody/tr/td[2]/text()')
    school_zhuguan_list2 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tr/td[2]/text()')
    
    school_loc_list1 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tbody/tr/td[3]/text()')
    school_loc_list2 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tr/td[3]/text()')

    school_cengci_list1 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tbody/tr/td[4]/text()')
    school_cengci_list2 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tr/td[4]/text()')
    
    school_leixing_list1 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tbody/tr/td[5]/text()')
    school_leixing_list2 = selector.xpath('//div[@class="WrapContent"]/div[@id!="Div0"]/table/tr/td[5]/text()') 
    
    school_name_list = school_name_list1+school_name_list2
    school_zhuguan_list = school_zhuguan_list1+school_zhuguan_list2
    school_loc_list = school_loc_list1+school_loc_list2
    school_cengci_list = school_cengci_list1+school_cengci_list2
    school_leixing_list = school_leixing_list1+school_leixing_list2
    ####存儲數據
    school_info = [['學校名稱','主管部門','所在地','層次','類型']]
    for j in range(len(school_name_list)):
        school_name = "/".join(school_name_list[j].xpath('descendant-or-self::text()'))#選取當前節點的所有后代元素(子、孫等)以及當前節點本身 
        school_name.replace('-','')
        school_name = re.search(u'[\u4e00-\u9fa5]+',school_name).group()#正則匹配中文
        school_zhuguan = (school_zhuguan_list[j]).strip()
        school_loc = school_loc_list[j].strip()
        school_cengci = school_cengci_list[j].strip()
        school_leixing = school_leixing_list[j].strip()
        if school_name!='學校名稱' or school_zhuguan!='主管部門' or school_loc!='所在地' or school_cengci!='層次' or school_leixing!='類型':
            school_info.append([school_name,
                                school_zhuguan,
                                school_loc,
                                school_cengci,
                                school_leixing                  
                                ])
    return school_info

3.5 數據獲取與解析

數據的獲取分為兩步,先通過selenium模擬瀏覽器解析得到數據XHR文件的guid,再通過構建XHR的URL進行訪問并解析,注意代理IP池的更新與爬取頻率,快遞100網的反爬蟲機制還是比較敏感的,所以在爬的時候頻率采用隨機小睡眠,定量大休眠的方式,具體見代碼。

def getGuids(keywords):
    '''
    功能:獲取數據
    @keywords:搜索關鍵詞
    '''
    guids = []
    pat = "\(\'(.*?)\'\)\;"  #ID匹配模式
    chromedriver = "C:/Users/whenif/AppData/Local/Google/Chrome/Application/chromedriver"
    i = 0
    j = 0
    for keyword in keywords:
        i += 1
        browser = webdriver.Chrome(chromedriver)  #模擬瀏覽器
        keyword = urllib.parse.quote(keyword) #URL編碼
        browser.get("https://www.kuaidi100.com/courier/?searchText="+keyword)
        ids = browser.find_elements(by=By.XPATH,value="http://div[@id='queryResult']/dl/dd[2]/span/a")  #構建XHR的ID
        print("正在爬取第"+str(i)+"個關鍵詞...")
        if i==80:
            time.sleep(180)#每爬取80個休息3分鐘
        else:
            seconds =  random.randint(8, 12)
            time.sleep(seconds)
        for id in ids:
            j += 1  #調試
            print("共爬取到"+str(j)+"個guid...")
            id = id.get_attribute('onclick')
            id = re.compile(pat).findall(id)
            guids.append([urllib.parse.unquote(keyword),id[0]])
        browser.quit()
    return guids


def getInfos(guids,proxy_pool):
    '''
    功能:獲取數據
    @guids:快遞員全局唯一標識列表
    @proxy_pool:代理IP池
    '''
    global data 
    infos = []#存儲最終所有信息
    i = 0  #代理IP循環調度累加器
    j = 1  #爬取個數累加器
    for guid in guids:
        URL = 'https://www.kuaidi100.com/courier/searchapi.do?method=courierdetail&json={"guid":"'+guid[1]+'"}'  #數據所在URL
        user_agent = getUserAgent()
        my_user_agent = ("User-Agent",user_agent)
        print("正在爬取第"+str(j)+"個快遞員信息...")
        j += 1 
        i += 1
        if len(proxy_pool)!=0 and i < len(proxy_pool):
            i=i
        elif len(proxy_pool)!=0 and i >= len(proxy_pool):
            i=0
        else:
            print("代理IP池資源已枯竭,正在更新代理IP池...")
            unchecked_proxies = getProxies(5)  #獲取原始代理IP
            checked_proxies = mulTestProxies(unchecked_proxies)#多線程測試原始代理IP   
            proxy_pool = []
            for tmp_proxy in checked_proxies:
                if len(tmp_proxy)!=0:
                    proxy_pool.append(tmp_proxy)
            print("代理IP池更新完畢,共獲取"+str(len(proxy_pool))+"個代理IP")
            i=0 
        proxy_addr = proxy_pool[i]
        proxy = urllib.request.ProxyHandler({"http":proxy_addr[0]})
        opener = urllib.request.build_opener(proxy,urllib.request.HTTPHandler)
        opener.addheaders=[my_user_agent]
        urllib.request.install_opener(opener)
        try:
            data = opener.open(URL).read() 
        except urllib.error.URLError as er2: 
            proxy_pool.remove(proxy_addr) #報錯則移除改IP
            if hasattr(er2,"code"):
                print("錯誤代碼:"+str(er2.code))
            if hasattr(er2,"reason"):
                print("錯誤原因:"+str(er2.reason))
        if type(data)==str:
            data = data
        else:
            data = data.decode()
        data_json = json.loads(data)
        company_name = data_json['courier']['companyName']  #公司名稱
        courier_name = data_json['courier']['courierName']  #快遞員姓名
        courier_tel = data_json['courier']['courierTel']  #手機號碼
        work_time = data_json['courier']['workTime']  #工作時間
        score = data_json['courier']['score']  #得分
        xzq_full_name = data_json['courier']['xzqFullName']  #地區
        infos.append([guid[0],guid[1],company_name,courier_name,courier_tel,work_time,score,xzq_full_name])
        # 控制爬取頻率,每100次爬取休息1分鐘的,其他的每次爬取休息3秒
        if i%100==0:
            time.sleep(60)
        else:
            time.sleep(3)
    return infos

3.6 數據存儲

def dbCon():
    '''
    功能:連接MySQL數據庫
    '''
    con = MySQLdb.connect(
        host='localhost',  # port
        user='***',       # usr_name
        passwd='***',     # passname
        db='***',  # db_name
        charset='utf8',
        local_infile = 1
        )
    return con  
 
def exeSQL(sql):
    '''
    功能:數據庫查詢函數 
    @sql:定義SQL語句
    '''
    global res
    print("exeSQL: " + sql)
    #連接數據庫
    con = dbCon()  #創建數據庫的連接
    cur = con.cursor()  #通過獲取到的數據庫連接conn下的cursor()方法來創建游標
    try:
        tmp = cur.execute(sql) #通過游標cur 操作execute()方法可以寫入純sql語句
        res = cur.fetchmany(tmp)#cur.fetchone()只會使游標不斷的向下移動
    except Exception as er:
        print('執行MySQL語句【' + str(sql) + '】時出如下錯誤:')        
        print(er)
    finally:
        cur.close()  #關閉游標
        con.commit()  #方法在提交事物,在向數據庫插入一條數據時必須要有這個方法,否則數據不會被真正的插入。
        con.close()  #關閉數據庫連接
    return res
    
def exeInsertSQL(sql,data_list):
    '''
    功能:數據庫插入函數 
    @sql:定義插入SQL語句
    @data_list:插入數據列表
    ''' 
    con = dbCon()  #創建數據庫的連接
    cur = con.cursor()
    try:
        n = cur.executemany(sql,data_list)
    except Exception as er:
        print('執行MySQL語句【' + str(sql) + '】時出如下錯誤:')        
        print(er)
    finally:
        cur.close()  #關閉游標
        con.commit()  #方法在提交事物,在向數據庫插入一條數據時必須要有這個方法,否則數據不會被真正的插入。
        con.close()  #關閉數據庫連接

def dataStore(school_info,xhr_guids,courier_info):
    '''
    功能:數據庫存儲 
    @school_info:學校信息
    @xhr_guids:數據XHR文件的全局唯一標識符
    @courier_info:快遞員數據
    ''' 
    #存儲學校信息
    table_name1 = 'school_info'
    exeSQL("drop table if exists " + table_name1)
    exeSQL("create table " + table_name1 + "(學校名稱 varchar(100), 主管部門 varchar(50), 所在地 varchar(50), 層次 varchar(50), 類型 varchar(50));")
    insert_sql1 = "insert into " + table_name1 + " values(%s,%s,%s,%s,%s);"
    exeInsertSQL(insert_sql1,school_info)       
    #存儲數據XHR文件的全局唯一標識符
    table_name2 = 'xhr_guids'
    exeSQL("drop table if exists " + table_name2)
    exeSQL("create table " + table_name2 + "(搜索關鍵詞 varchar(100),全局標識符 varchar(50));")   
    insert_sql2 = "insert into " + table_name2 + " values(%s,%s);"
    exeInsertSQL(insert_sql2,xhr_guids)
    #存儲快遞員數據
    table_name3 = 'courier_info'
    exeSQL("drop table if exists " + table_name3)
    exeSQL("create table " + table_name3 + "(`搜索關鍵詞` varchar(100),`全局標識符` varchar(50),`所屬公司` varchar(50),`快遞員姓名` varchar(20),`手機號碼` varchar(20),`工作時間` varchar(100),`得分` varchar(30),`所屬地區` varchar(50));")   
    insert_sql3 = "insert into " + table_name3 + " values(%s,%s,%s,%s,%s,%s,%s,%s);"
    exeInsertSQL(insert_sql3,courier_info)
 

3.7 主函數調用

def main():
    '''
    功能:主函數,調用相關函數    
    '''
    #---(1)獲取初始代理IP池
    unchecked_proxies = getProxies(10)  #獲取原始代理IP
    checked_proxies = mulTestProxies(unchecked_proxies)  #多線程測試原始代理IP   
    proxy_pool = []
    for tmp_proxy in checked_proxies:
        if len(tmp_proxy)!=0:
            proxy_pool.append(tmp_proxy)
            print("代理IP池獲取完畢,共獲取"+str(len(proxy_pool))+"個代理IP")
    #---(2)獲取地理位置(搜索關鍵詞)
    school_info = getSchoolInfo()  #獲取全省高校信息
    final_loc = []  #提取搜索關鍵詞
    for loc in school_info[1:]:
        final_loc.append(loc[0])
    '''預留全省街道信息獲取接口
    sel_sql = 'select `province_name`\
                    ,`city_name`\
                    ,`county_name`\
                    ,`town_name`\
                    ,`village_name`\
            from    positionV1 ' +\
            'where  `province_name`="廣東省" \
               and  city_name="廣州市";'
    location = exeSQL(sel_sql)
    final_loc = []
    for loc in location:
        loc = loc[1]+loc[2]+loc[3]+loc[4]
        final_loc.append(loc)'''
    #---(3)獲取數據
    xhr_guids = getGuids(final_loc)
    courier_info = getInfos(guids,proxy_pool) #爬取快遞號碼信息
    #---(4)存儲數據
    dataStore(school_info,xhr_guids,courier_info)
  
if __name__ == "__main__":
    main()

4 結果與可視化

4.1 結果表結構

爬取數據并入庫之后結果如下:


廣東大學信息表

信息表

該結果只是簡單利用學校名稱進行搜索得到的結果,同一學校不同分校區均劃分算一學校,如中山大學有東校區和南校區,所以結果會有一定誤差。如要獲取全廣州快遞號碼,可以通過調用四級聯動街道數據庫接口進行查詢。

4.2 數據可視化

該爬蟲共爬取快遞員信息2052條,利用快遞員號碼進行剔重之后剩下1469條,學??偣灿?42個(本科62、???0),每個學校平均大約有10個快遞員。

(1)快遞員公司分布

(2)快遞員區域分布

全省各地市高??爝f員分布

廣州高??爝f員分布

全省高校快遞員數量前三名為廣州、東莞、佛山,廣州市內前三名分別是天河、白云和番禺。

(3)快遞員高校分布

以上結果很大程度上取決與不同分類學校的數量,如廣州的高校數量肯定大于其他地市,所以爬取結果的快遞員數量肯定多于其他地市,其他同理。但是值得注意的是快遞員手機號碼是有很重要的特征,如打出次數遠超打入次數,平均通話時長較短、發送信息量較多等,從運營商數據角度看,快遞員如同中介作為一種特殊的人群,其號碼群對于模型的建立與校驗還是有一定價值的。


本文所有代碼只用于技術交流,拒絕任何商用活動
文章相關項目代碼已上傳至個人Github
個人博客DebugNLP
歡迎各路同學互相交流

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容