Python爬蟲(chóng):細(xì)說(shuō)列表識(shí)別提取

天冷要保暖

??上次文章后不少小伙伴私信我,對(duì)此感興趣,希望我講講列表識(shí)別的細(xì)節(jié)問(wèn)題。于是有了今天這篇文章。還是先再提一下本算法的核心思想。

  • 排列規(guī)則的鏈接為可以列表塊。
  • 列表塊范圍在主視圖區(qū)域內(nèi)為目標(biāo)列表。
    先看下識(shí)別結(jié)果:
    列表識(shí)別結(jié)果圖

    提取列表區(qū)域xpath宏觀(guān)分成三個(gè)大步驟

1.可疑列表區(qū)域提取

??在進(jìn)行可疑列表區(qū)域提取之前需要做一些預(yù)處理:因?yàn)閟elenium只能定位到頁(yè)面上的可見(jiàn)元素,所以先用selenium的find_elements_by_xpath("http://a")獲取所有可見(jiàn)的<a>,并對(duì)定位到的元素創(chuàng)建新屬性canSee并賦值yeap:self.__web_driver.execute_script("arguments[0].setAttribute('can-see','yeap');", link)(其實(shí)你想屬性叫什么就叫什么),然后就清洗完畢了。
??接下來(lái)用lxml庫(kù)的etree定位到dom樹(shù)(這里將html直接說(shuō)成dom樹(shù)是為了后面提取最小父節(jié)點(diǎn)時(shí)候好理解)上所有canSee屬性是True的<a>標(biāo)簽節(jié)點(diǎn)。將該節(jié)點(diǎn)假如列表A,以三個(gè)元素為單位掃描該列表,如下圖。

可疑列表區(qū)域掃描過(guò)程示意圖

Tips:
  • 在整個(gè)dom樹(shù)下,同一子樹(shù)同一層級(jí)的節(jié)點(diǎn)才會(huì)提取最小父節(jié)點(diǎn)(最小父節(jié)點(diǎn):層級(jí)盡可能的小
  • 重復(fù)父節(jié)點(diǎn)的xpath不要重復(fù)計(jì)入
  • 有的<a>標(biāo)簽下取text會(huì)出現(xiàn)問(wèn)題,最好用xpath的string(.)方式
  • 元素清洗時(shí)可以初步匹配明顯反向特征,匹配成功直接退出

??在三大步驟中,只有這一步是在做加法,剩下的步驟基本是在過(guò)濾做減法了,所以盡可能的將可疑列表區(qū)域收入列表。

代碼流程參考:
def tag_a_min_father_node(self):
    """
    計(jì)算提取可疑列表區(qū)域        
    :return: [xpath1,xpath2,xpath3,...]
    """
    links_Ele = []
    father_list = []
    # 預(yù)處理-將可見(jiàn)<a>設(shè)置屬性can-see
    self.watch_links()

    root = etree.HTML(self.driver.page_source)
    Eleroot = etree.ElementTree(root)
    temp_total_path = []
    links =  Eleroot.findall('//a[@cansee]')
    self.LOG.info("all links after filter: {}".format(len(links)))
    # 識(shí)別時(shí)忽略JavaScript,因?yàn)楹罄m(xù)步驟沒(méi)有上下文環(huán)境
    links_Ele = [(x.xpath("string(.)")..strip(),\
                  Eleroot.getpath(x), \
                  x.attrib.get("href","")) \
                  for x in links \
                  if x.xpath("string(.)"). and len(x.xpath("string(.)").strip()) > 1 and \
                       self.anchor_black_regx.search(x.xpath("string(.)").strip()) is None\
                      and self.debar_extension_name_regex.search(x.attrib.get("href","")) is None \
                      and not x.attrib.get("href","").startswith("java")\
                      and not x.attrib.get("href","").startswith("#") # 不要錨鏈接
                     ]
    # 元素清洗
    # 相鄰標(biāo)簽相同href,合并
    # 如果匹配到反向特征 legitimate = False
    legitimate, links_Ele = self.clean_links_Ele(links_Ele)

    if legitimate:
        # 掃描有效鏈接,提取最小父節(jié)點(diǎn)xpath
        for idx in xrange(len(links_Ele)-2):
            # 每次取三個(gè)元素
            now = links_Ele[idx: idx+3]
            is_list, father_xpath = self.get_list_father_xpath(now)
            if is_list:
                # 符合列表邏輯
                father_list.append(father_xpath)

    return list(set(father_list))

2.過(guò)濾不在主視圖區(qū)域的可疑列表

  • 2.1 校驗(yàn)x軸
    ??在這該步驟中,校驗(yàn)可疑列表區(qū)域是否在主視圖范圍內(nèi)。你需要了解selenium的location方法,了解(x,y)坐標(biāo)點(diǎn)在瀏覽器中的意義,在該算法中,使用x軸中位線(xiàn)作為判斷依據(jù)。
    ??現(xiàn)有列表區(qū)域A,其location為(x1,y1)。列表A中,有最大鏈接b,其size['width']為x2。若x1+x2 > x軸中位線(xiàn),則列表A在主視圖范圍內(nèi)。
    ??看下圖,不難理解:
    紅線(xiàn)為x軸中位線(xiàn)

代碼流程參考:

def judge_list_xpath(self):
    """
    判斷獲取到的列表xpath是否在主視圖區(qū)域
    :return:[xpath1,xpath2]
    """
    a_list= []
    list_xpath = []
    result = []
    # 獲取可疑列表區(qū)域
    list_xpath = self.get_page_list()

    if list_xpath:
        for item in list_xpath:
            a_size_list = []
            try:
                a_list = self.driver.find_elements_by_xpath(item + '//a')
            except:
                self.LOG.error("{}:{}無(wú)法找到該xpath" .format(self.driver.current_url, item + '//a'))

            for element in a_list:
                a_size_list.append(element.size['width'])
            # 有的html可能不規(guī)范,會(huì)出現(xiàn)定位不到元素的情況
            max_a_size = max(a_size_list) if len(a_size_list) > 0 else 0
            if max_a_size == 0: continue
            # 判斷size最大的a標(biāo)簽的位置
            content = self.driver.find_element_by_xpath(item)
            # 超過(guò)3000認(rèn)為異常情況
            if content.size['width'] > 3000:
                continue
            # 這句無(wú)所謂,原來(lái)想用來(lái)過(guò)濾導(dǎo)航欄之類(lèi)的,現(xiàn)在后續(xù)有更好解決方案
            if (content.size['height']) < 70 and content.size['height'] != 0:
                continue
            # 判斷x軸中位線(xiàn)
            if self.check_x(content):
                    result.append(item)
    self.LOG.info("list after view filter: {}".format(result))
    return result
  • 2.2校驗(yàn)y軸
    ??這一步需要放在程序最后,規(guī)則也比較簡(jiǎn)單,最后校驗(yàn)列表的location['y']是否在瀏覽器的當(dāng)前頁(yè)面中,我認(rèn)為如果你打開(kāi)網(wǎng)頁(yè),一下看不見(jiàn)列表,需要往下拖才有列表,就不是我們需要的主列表了,可能是混進(jìn)來(lái)奇奇怪怪的東西,邏輯比較簡(jiǎn)單就不貼代碼流程了。

3.可擴(kuò)展規(guī)則簇

??以上步驟基本可以保證你獲得一個(gè)穿過(guò)了x中位線(xiàn)的列表區(qū)域,但極有可能混進(jìn)去一些奇奇怪怪的東西,或者漏了一些重要的東西。這時(shí)候就需要你的這些規(guī)則了,比如:

  • 多塊列表跨x中位線(xiàn)
  • 識(shí)別到導(dǎo)航欄或者識(shí)別到滾動(dòng)欄中的新聞,不需要這種東西,需要過(guò)濾
    • 過(guò)濾規(guī)則很簡(jiǎn)單,校驗(yàn)xpath中<a>的y坐標(biāo),極大值與極小值需要超過(guò)一個(gè)閥值
  • 中央?yún)^(qū)域含有文本為更多的鏈接,我相信這種列表也不是我們需要的
    還有后續(xù)其他的規(guī)則往上追加就好OvO

至此列表區(qū)域識(shí)別已經(jīng)完成,輸出值為列表區(qū)域的xpath。
有問(wèn)題的話(huà)私聊我吧,沒(méi)問(wèn)題的話(huà)點(diǎn)贊吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容