1. 程式人生 > >做一個爬蟲專用的url解析器

做一個爬蟲專用的url解析器

思路分析

首先是獲取三個或以上的url(帶引數的url)

然後通過字串方法將url中的基礎url跟引數分離開

通過對比多個url中的同一個引數的值,

將引數分成三類

一類為不變的引數,一類為會改變的引數,還有一類是時有時無(可有可無)的引數

最後做成GUI,介面設想如下

在左上方的text窗輸入url,點選加入,在右側列表窗會顯示已新增的url,如果存在希望刪除的url,點選列表窗中對應的url,點選刪除即可從列表窗中移除,點選解析,即對url進行解析

程式碼實現

GUI部分

QT介面雖易做,可惜還沒時間去研究QT的gui後臺程式碼怎麼實現,所以還是用tkinter好了

以下是介面程式碼

from tkinter import *
import tkinter.messagebox
import re
from urlparser import URLparser
from threading import Timer
import time
import json


def ListboxClick(event):
    keyname = listbox_result.get(ACTIVE)
    content = json.loads(label_hidden["text"])
    label_result["text"] = content[keyname]


def timmer():
    now = "現在時間 >>>  "
    now += time.strftime("%Y - %m - %d   %H : %M : %S")
    label_time["text"] = now
    timer = Timer(1, timmer)
    timer.start()


def Button_rebootClick(event):
    result = tkinter.messagebox.askquestion(title="提示", message="是否重置?")
    if result == "no":
        return None
    text_input.delete(0.0, END)
    listbox_show.delete(0, 29)
    label_result["text"] = ""
    label_number["text"] = "url個數:0"


def Button_joinClick(event):
    num = int(label_number["text"].split(":")[-1])
    if num == 30:
        return None
    # 獲得輸入框內容
    url = text_input.get(0.0, END)[0:-1]
    # 刪除輸入框內容
    text_input.delete(0.0, END)
    # 匹配正則表示式,是否為空
    space_reg = re.compile(r"^\s*$")
    if space_reg.match(url):
        return None
    # 匹配正則表示式,是否為正常的url格式
    url_reg = re.compile(r"^(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]$")
    if not url_reg.match(url):
        result = tkinter.messagebox.askquestion(title="url匹配提示", message="您輸入的url格式可能不正確,確定要新增?")
        if result == "no":
            return None
    # 加入列表框
    listbox_show.insert(END, url)
    num += 1
    # 修改個數
    label_number["text"] = "url個數:" + str(num)


def Button_clearClick(event):
    num = int(label_number["text"].split(":")[-1])
    if num == 0:
        return
    # 刪除列表框選中內容
    listbox_show.delete(ACTIVE)
    num -= 1
    # 修改個數
    label_number["text"] = "url個數:" + str(num)


def Button_parseClick(event):
    # 第一步,取得列表框存放的所有url
    reg = re.compile(r"^\s*$")
    url_list = []
    index = 0
    while True:
        url = listbox_show.get(index)
        if reg.match(url):
            break
        url_list.append(url)
        index += 1
    # 第二步,傳入後臺處理,返回結果
    parser = URLparser.UrlParser()
    result = parser.parse(url_list)
    for key, value in result.items():
        listbox_result.insert(END, key)
        if key.startswith("主體url:"):
            label_result["text"] = value
    # 第三步,將獲得的結果放到隱藏標籤裡
    result = json.dumps(result)
    label_hidden["text"] = result


# 主視窗
window = Tk()
window.maxsize(1000, 600)
window.minsize(1000, 600)
window.title("帶引數url解析器")
# 文字輸入框
Label(window, text="請在下方輸入框輸入url", font=(15,)).place(x=20, y=10)
text_input = Text(window, width=50, height=10, borderwidth=5, relief=SUNKEN, font=(15,))
text_input.place(x=20, y=40)
# url列表框
Label(window, text="已輸入的url列表", font=(15,)).place(x=570, y=10)
label_number = Label(window, text="url個數:0", font=(15,))
label_number.place(x=890, y=10)
# # 框架
frame_listbox_show = Frame(window)
frame_listbox_show.place(x=570, y=40)
# # 滾動條
show_scx = Scrollbar(frame_listbox_show, orient=HORIZONTAL)
show_scx.pack(side=BOTTOM, fill=X)
show_scy = Scrollbar(frame_listbox_show)
show_scy.pack(side=RIGHT, fill=Y)
# # url列表框
listbox_show = Listbox(frame_listbox_show, width=48, height=28, borderwidth=5, relief=SUNKEN, font=(15,),
                       xscrollcommand=show_scx.set, yscrollcommand=show_scy.set)
listbox_show.pack(side=LEFT, fill=BOTH)
show_scx.config(command=listbox_show.xview)
show_scy.config(command=listbox_show.yview)

# 結果列表框
Label(window, text="url解析結果如下", font=(15,)).place(x=20, y=215)
# # 框架
frame_listbox_result = Frame(window)
frame_listbox_result.place(x=20, y=242)
# # 滾動條
result_scx = Scrollbar(frame_listbox_result, orient=HORIZONTAL)
result_scx.pack(side=BOTTOM, fill=X)
result_scy = Scrollbar(frame_listbox_result)
result_scy.pack(side=RIGHT, fill=Y)

# # 結果列表框
listbox_result = Listbox(frame_listbox_result, width=28, height=17, relief=SUNKEN, font=(15,), borderwidth=5,
                         xscrollcommand=result_scx.set, yscrollcommand=result_scy.set)
listbox_result.pack(side=LEFT)
result_scx.config(command=listbox_result.xview)
result_scy.config(command=listbox_result.yview)

label_hidden = Label(window, width=10, height=10, text="這是隱藏標籤")
label_hidden.place(x=300, y=250)

label_result = Label(window, font=(15,), borderwidth=5, relief=SUNKEN, width=30, height=19)
label_result.place(x=295, y=242)

# 按鈕
# # 加入
button_join = Button(window, text="加  入", width=10, height=1, font=(15,), borderwidth=5, relief=RAISED)
button_join.place(x=450, y=50)
# # 刪除
button_clear = Button(window, text="刪  除", width=10, height=1, font=(15,), borderwidth=5, relief=RAISED)
button_clear.place(x=450, y=108)
# # 解析
button_parse = Button(window, text="解  析", width=10, height=1, font=(15,), borderwidth=5, relief=RAISED)
button_parse.place(x=450, y=166)

# 底部欄
button_reboot = Button(window, text="重  置", width=10, height=1, borderwidth=5, relief=RAISED, font=(15,))
button_reboot.place(x=882, y=562)
now = "現在時間 >>>  "
now += time.strftime("%Y - %m - %d   %H : %M : %S")
label_time = Label(window, text=now, font=(15,))
label_time.place(x=20, y=570)

# 繫結事件
# # 加入按鈕左鍵點選事件
button_join.bind("<ButtonRelease-1>", Button_joinClick)
# # 刪除按鈕左鍵點選事件
button_clear.bind("<1>", Button_clearClick)
# # 解析按鈕左鍵點選事件
button_parse.bind("<1>", Button_parseClick)
# # 重置按鈕左鍵點選事件
button_reboot.bind("<ButtonRelease-1>", Button_rebootClick)
# # 列表框點選事件
listbox_result.bind("<1>", ListboxClick)
# 設定定時器
timer = Timer(1, timmer)
timer.start()
# 主視窗執行
window.mainloop()

暫時還沒有封裝成類,介面也一般,不過這次加了定時器,可以定時重新整理內容,牛刀小試,每秒重新整理一次時間好了

介面看著其實還不賴

 

兩個結果窗顯示的原理大概就是將加入的url送到後臺,解析後得到的結果是一個字典

左邊的列表框顯示的是字典的鍵,右邊的標籤顯示的是單一某鍵對應的鍵值,一般就是某個引數的結果羅列

通過點選右側的列表框的具體內容決定右側標籤顯示何種內容

在顯示結果的標籤框 label_result 背後由一個專門存放全部資料的隱藏標籤,即是程式碼中的 label_hidden

需要切換內容的時候即從 label_hidden 中提取出來資料以呼叫

後端url處理部分

通過前端得到的一個列表

首先分析一下url是否為有?存在的帶參url,如果不是,則返回帶有錯誤提示資訊的字典

 

然後遍歷分析url主體是否一致(為了避免有時手殘輸錯別的url),確定一致了才僅需進行引數解析,但需要注意的是,即便是同一個網站,也會有出現http或https各種開頭的情況,為了避免這種狀況,url主體只取到https://後面到?前面的部分來進行對比

 

接下來就是引數分析,其實也簡單,以&分割引數,獲得引數鍵值對列表,再對引數列表進行遍歷,以=分割鍵與值,將擁有相同鍵名的鍵值存放到另一字典中相同鍵名下的列表裡

 

最後將存放參數的字典進行遍歷,

第一步分析是否為可預設引數,鍵值中的引數列表的長度小於輸入url列表的長度即為存在預設,意味著該引數可預設,如果是可預設,continue繼續下一次迴圈

第二步分析是否為不變引數,計算鍵值中的引數列表的集合長度,如果為1,即為不變引數

否則則是或改變引數

 

最後將分析結果做成字典,輸出到前端

 

具體程式碼如下

import re


class UrlParser(object):
    def parse(self, url_list):
        '''
        :param url_list: 一個至少包含3個url的列表
        :return:
        '''
        key_list = []
        result = {}
        for index, url in enumerate(url_list):
            # 第一步,分離url主體,若存在不相同,返回提示
            if "?" not in url:
                return {str(index + 1): "第%d個url不屬於帶引數型url" % (index + 1)}
            all_part = url.split("?")
            url_reg = re.compile(r"^(https?|ftp|file)://([-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])$")
            uurl = url_reg.findall(all_part[0])[0][1]
            if index > 0:
                if uurl != main_url:
                    return {str(index + 1): "第%d個url的主體url與其前面的url主體不相同" % (index + 1)}
            else:
                main_url = uurl
            all_params = all_part[1]
            # 第二步,分離引數
            param = all_params.split("&")
            # 第三步,分離鍵值對
            for p in param:
                k_v = p.split("=")
                key = k_v[0]
                value = k_v[1]
                if key not in key_list:
                    result[key] = []
                    key_list.append(key)
                result[key].append(value)

        # 解析引數
        num = len(url_list)
        output = {"主體url:" + main_url: "雙擊左側參\n數檢視詳情"}

        for key, value in result.items():
            # 判斷是否為可預設引數
            if len(value) < num:
                output["引數可預設: " + key] = key + " : \n" + "\n".join(value) + "\nNone" * (num - len(value))
                continue
            # 判斷是否為不變引數
            if len(set(value)) == 1:
                output["引數不變: " + key] = key + " : \n" + "\n".join(value)
            else:
                output["引數或改變: " + key] = key + ": \n" + "\n".join(value)
        return output

結果展示

取了百度貼吧三個搜尋結果下的url,分別是python,matlab,c

http://tieba.baidu.com/f?ie=utf-8&kw=python&fr=search

http://tieba.baidu.com/f?ie=utf-8&kw=matlab&fr=search

http://tieba.baidu.com/f?ie=utf-8&kw=c&fr=search

加入到列表框之後,點選""解析""