最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

python使用pywinauto驅(qū)動(dòng)微信客戶端實(shí)現(xiàn)公眾號(hào)爬蟲(chóng)

 更新時(shí)間:2021年05月19日 16:12:33   作者:李理  
這個(gè)項(xiàng)目是通過(guò)pywinauto控制windows(win10)上的微信PC客戶端來(lái)實(shí)現(xiàn)公眾號(hào)文章的抓取。代碼分成server和client兩部分。server接收client抓取的微信公眾號(hào)文章,并且保存到數(shù)據(jù)庫(kù)。另外server支持簡(jiǎn)單的搜索和導(dǎo)出功能。client通過(guò)pywinauto實(shí)現(xiàn)微信公眾號(hào)文章的抓取。

項(xiàng)目地址

https://github.com/fancyerii/wechat-gongzhonghao-crawler

pywinauto簡(jiǎn)介

pywinauto是一個(gè)python的工具,可以用于控制Windows的GUI程序。詳細(xì)的文檔可以參考這里。

WechatAutomator類

自動(dòng)化微信的代碼封裝在了類WechatAutomator里,完整的代碼可以參考這里。這里簡(jiǎn)要的介紹一下其中的主要方法:

init_window

這個(gè)方法完成類的初始化,它的代碼為:

    def init_window(self, exe_path=r"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe",
                    turn_page_interval=3,
                    click_url_interval=1,
                    win_width=1000,
                    win_height=600):
        app = Application(backend="uia").connect(path=exe_path)
        self.main_win = app.window(title=u"微信", class_name="WeChatMainWndForPC")
        self.main_win.set_focus()
        self.app = app
        self.visible_top = 70
        self.turn_page_interval = turn_page_interval
        self.click_url_interval = click_url_interval
        self.browser = None
        self.win_width = win_width
        self.win_height = win_height
        # 為了讓移動(dòng)窗口,同時(shí)使用非uia的backend,這是pywinauto的uia的一個(gè)bug
        self.app2 = Application().connect(path=exe_path)
        self.move_window()

我們首先來(lái)看函數(shù)的參數(shù):

  • exe_path
    • 微信程序的地址
  • turn_page_interval
    • 抓取翻頁(yè)時(shí)的時(shí)間間隔,默認(rèn)3s
  • click_url_interval
    • 在抓取一頁(yè)的url時(shí)的間隔,默認(rèn)1s
  • win_width
    • 設(shè)置窗口的寬度
  • win_height
    • 設(shè)置窗口的高度,如果顯示器的分辨率較大,可以設(shè)置的更加高一些,從而一頁(yè)包含的文章數(shù)更多一些,從而翻頁(yè)少一點(diǎn)。注意:一定要保證窗口完全可見(jiàn),也就是說(shuō)win_height不能大于實(shí)際分辨率的高度!

這個(gè)函數(shù)的主要功能是構(gòu)建Application對(duì)象從而通過(guò)pywinauto實(shí)現(xiàn)控制,這里使用的是uia的backend,然后設(shè)置窗口的大小并且把窗口移到最左上角。因?yàn)楦鶕?jù)so文章,pywinauto 0.6.8存在bug,只能通過(guò)win32的backend來(lái)移到窗口,所以構(gòu)造了self.app2然后調(diào)用move_window()函數(shù)把窗口移到最左上角。

crawl_gongzhonghao

這個(gè)函數(shù)實(shí)現(xiàn)了某個(gè)公眾號(hào)的文章抓取。它的基本控制邏輯如下:

  • 首先通過(guò)搜索框根據(jù)名字搜索公眾號(hào)并且點(diǎn)擊它。
  • 對(duì)于當(dāng)前頁(yè)點(diǎn)擊所有的鏈接并且下載其內(nèi)容。
  • 使用PAGE_DOWN鍵往下翻頁(yè)
  • 需要判斷是否繼續(xù)抓取

第一個(gè)是通過(guò)locate_user函數(shù)實(shí)現(xiàn),后面會(huì)介紹。第二個(gè)是通過(guò)process_page函數(shù)實(shí)現(xiàn),后面也會(huì)介紹。判斷是否繼續(xù)抓取的邏輯為:

  • 如果翻頁(yè)超過(guò)max_pages,則停止抓取
  • 如果碰到某個(gè)url曾經(jīng)抓取過(guò),那說(shuō)明之前的文章都已經(jīng)抓取過(guò)了,則停止抓取
  • 如果lastest_date不是None并且一篇文章的發(fā)布日期早于它,則停止抓取

所以我們通常會(huì)在第一次抓取的時(shí)候把max_pages設(shè)置的很大(比如100),然后通過(guò)latest_date來(lái)抓到指定的日期。而之后的抓取則設(shè)置max_pages為較小的值(比如默認(rèn)的6),這樣只要爬蟲(chóng)在兩次抓取之間公眾號(hào)的更新不超過(guò)6頁(yè),那么就不會(huì)漏掉文章。具體的邏輯可以參考main.py,它會(huì)把抓取的文章通過(guò)http請(qǐng)求發(fā)給Server,并且每次抓取的時(shí)候從Server查詢抓取過(guò)的文章存放到states這個(gè)list里states[i][“url”]就存儲(chǔ)了第i篇文章的url。

    def crawl_gongzhonghao(self, account_name, articles, states, detail,
                           max_pages=6, latest_date=None, no_item_retry=3):
        logger.debug(account_name)
        if not self.locate_user(account_name):
            return False
        last_visited_titles = set()
        visited_urls = set()
        self.turn_page_up(min(20, max_pages * 2))

        pagedown_retry = 0
        last_visited_titles = []
        for page in range(0, max_pages):
            items = []
            last_visited_titles = self.process_page(account_name, items, last_visited_titles, states, visited_urls, detail)
            articles.extend(items)

            if len(items) == 0:
                pagedown_retry += 1
                if pagedown_retry >= no_item_retry:
                    s = "break because of retry {}".format(pagedown_retry)
                    logger.debug(s)
                    WechatAutomator.add_to_detail(s, detail)
                    break
            else:
                pagedown_retry = 0

            if len(items) > 0 and latest_date is not None:
                html = items[-1][-1]
                pub_date = WechatAutomator.get_pubdate(html)
                if pub_date and pub_date < latest_date:
                    s = "stop because {} < {}".format(pub_date, latest_date)
                    logger.debug(s)
                    WechatAutomator.add_to_detail(s, detail)
                    break

            url_exist = False
            for item in items:
                if WechatAutomator.url_in_states(item[0], states):
                    s = "stop because url exist {}".format(item[0])
                    logger.debug(s)
                    WechatAutomator.add_to_detail(s, detail)
                    url_exist = True
                    break
            if url_exist:
                break

            self.click_right()
            self.main_win.type_keys("{PGDN}")
            time.sleep(self.turn_page_interval)

        self.turn_page_up(page * 2)

        return True

locate_user

locate_user函數(shù)的控制流程為:

  • 找到左上角的搜索框并且點(diǎn)擊它獲得焦點(diǎn)
  • 使用ctrl+a選中可能有的文字(之前的bug?)并且使用后退鍵刪除它們
  • 輸入公眾號(hào)名稱
  • 在彈出的list里點(diǎn)擊這個(gè)公眾號(hào)名稱從而進(jìn)入公眾號(hào)
    def locate_user(self, user, retry=5):
        if not self.main_win:
            raise RuntimeError("you should call init_window first")

        search_btn = self.main_win.child_window(title="搜索", control_type="Edit")
        self.click_center(search_btn)

        self.main_win.type_keys("^a")
        self.main_win.type_keys("{BACKSPACE}")
        self.main_win.type_keys(user)
        for i in range(retry):
            time.sleep(1)
            try:
                search_list = self.main_win.child_window(title="搜索結(jié)果")
                match_result = search_list.child_window(title=user, control_type="ListItem")
                self.click_center(match_result)
                return True
            except:
                pass

        return False

這里主要就是通過(guò)child_window函數(shù)進(jìn)行定位,關(guān)于它的用法這里不介紹。關(guān)于怎么定位元素的方法可以使用Inspect.exe或者print_control_identifiers函數(shù),具體參考這里。

process_page

這個(gè)函數(shù)是最主要的抓取代碼,它處理當(dāng)前一頁(yè)的內(nèi)容,它的控制流程如下:

  • 構(gòu)建當(dāng)前頁(yè)的tree
  • 使用recursive_get函數(shù)遍歷這顆樹(shù)并且找到每篇文章對(duì)應(yīng)的element
  • 遍歷每一篇文章
    • 如果文章的名字在上一頁(yè)出現(xiàn)過(guò),則跳過(guò)
    • 獲得這篇文章的坐標(biāo)信息
    • 如果文章不可見(jiàn)(rect.top >= win_rect.bottom or rect.bottom <= self.visible_top)則跳過(guò)
    • 計(jì)算點(diǎn)擊的坐標(biāo)
    • 點(diǎn)擊文章打開(kāi)新的窗口
    • 在新的窗口中點(diǎn)擊【復(fù)制鏈接】按鈕
    • 從剪貼板復(fù)制鏈接url
    • 通過(guò)url下載文章內(nèi)容并且parse發(fā)布日期

邏輯比較簡(jiǎn)單,但是有一些很trick的地方:

  • 微信翻頁(yè)的實(shí)現(xiàn)
    • 微信客戶端的翻頁(yè)和瀏覽器不同,它的內(nèi)容是累加的,比如第一頁(yè)3篇文章,往下翻一頁(yè)可能變成6篇文章,再翻可能變成9篇。這個(gè)時(shí)候這9篇文章都是在tree中的,只不過(guò)最后3篇的坐標(biāo)(top和bottom)是空間的。
  • 能否點(diǎn)擊 一篇文章對(duì)應(yīng)的框(圖)可能是部分可見(jiàn)的,甚至它的top非常接近屏幕的最下方,這個(gè)時(shí)候可能點(diǎn)不了。如下圖所示:

與此類似的是右上角的黑色頭部(不能滾到并且會(huì)遮擋)也有一定空間,如下圖所示:

  • 點(diǎn)擊的位置

因?yàn)檫@個(gè)框可能很窄(bottom-top很小)并且可能在很靠上或者靠下的位置。所以有如下代碼:

    # 計(jì)算可見(jiàn)的高度
    visible_height = min(rect.bottom, win_rect.bottom) - max(rect.top, win_rect.top+self.visible_top)
    # 太窄的不點(diǎn)擊,希望下次翻頁(yè)后能顯示更多像素從而可以點(diǎn)擊,
    # 但是如果微信的某個(gè)文章的框的高度小于10個(gè)像素,那么這篇文章就無(wú)法被點(diǎn)擊
    # 不過(guò)作者目前為發(fā)現(xiàn)這么窄的文章
    if visible_height < 10:
        continue
    
    # 如果某個(gè)文章的框太大,則拋出異常,目前為止為發(fā)現(xiàn)這樣的問(wèn)題。
    if rect.bottom - rect.top >= win_rect.bottom - self.visible_top:
        raise RuntimeError("{}-{}>={}-{}".format(rect.bottom, rect.top,
                                                 win_rect.bottom, self.visible_top))
    # 如果下部部分可見(jiàn),那么點(diǎn)擊上方是比較”安全“的
    if rect.bottom >= win_rect.bottom:
        click_up = True
    # 如果下部完全可見(jiàn),則點(diǎn)擊下方是”安全“的
    else:
        click_up = False

完整代碼如下:

    def process_page(self, account_name, items, lastpage_clicked_titles, states, visited_urls, detail):
        clicked_titles = set()
        text = self.main_win.child_window(title=account_name, control_type="Text", found_index=0)
        parent = text
        while parent:
            parent = parent.parent()
            if '會(huì)話列表' == parent.element_info.name:
                break
        paths = [0, 2, 0, 0, 0, 1, 0]
        for idx in paths:
            parent = parent.children()[idx]

        elems = []
        self.recursive_get(parent, elems)
        win_rect = self.main_win.rectangle()
        for elem in elems:
            rect = elem.rectangle()

            if elem.element_info.name in lastpage_clicked_titles:
                continue

            if rect.top >= win_rect.bottom or rect.bottom <= self.visible_top:
                continue

            visible_height = min(rect.bottom, win_rect.bottom) - max(rect.top, win_rect.top+self.visible_top)
            if visible_height < 10:
                continue

            if rect.bottom - rect.top >= win_rect.bottom - self.visible_top:
                raise RuntimeError("{}-{}>={}-{}".format(rect.bottom, rect.top,
                                                         win_rect.bottom, self.visible_top))
            if rect.bottom >= win_rect.bottom:
                click_up = True
            else:
                click_up = False
            if self.is_bad_elem(elem):
                s = "not good elem {}".format(elem.element_info.name[0:10])
                logger.debug(s)
                WechatAutomator.add_to_detail(s, detail)
                continue

            try:
                self.click_url(rect, win_rect, click_up)
                copy_btn = self.browser.child_window(title="復(fù)制鏈接地址")
                self.click_center(copy_btn, click_main=False)
                url = clipboard.GetData()
                if elem.element_info.name != '圖片':
                    clicked_titles.add(elem.element_info.name)
                if url and not url in visited_urls:
                    visited_urls.add(url)
                    html = None
                    try:
                        html = requests.get(url).text
                    except:
                        s = "fail get {}".format(url)
                        logger.debug(s)
                        WechatAutomator.add_to_detail(s, detail)

                    items.append((url, rect, elem.element_info.name, html))

            except:
                traceback.print_exc()
                pass
            finally:
                if self.browser:
                    try:
                        self.browser.close()
                    except:
                        pass
                    self.browser = None

            time.sleep(self.click_url_interval)

        return clicked_titles

以上就是python使用pywinauto驅(qū)動(dòng)微信客戶端實(shí)現(xiàn)公眾號(hào)爬蟲(chóng)的詳細(xì)內(nèi)容,更多關(guān)于python 公眾號(hào)爬蟲(chóng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Pytorch?nn.Unfold()?與?nn.Fold()圖碼詳解(最新推薦)

    Pytorch?nn.Unfold()?與?nn.Fold()圖碼詳解(最新推薦)

    這篇文章主要介紹了Pytorch?nn.Unfold()?與?nn.Fold()圖碼詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • Cpython解釋器中的GIL全局解釋器鎖

    Cpython解釋器中的GIL全局解釋器鎖

    這篇文章主要介紹了Cpython解釋器中的GIL全局解釋器鎖的相關(guān)資料,幫助大家更好的了解Cpython解釋器,感興趣的朋友可以了解下
    2020-11-11
  • Python實(shí)現(xiàn)字符串與數(shù)組相互轉(zhuǎn)換功能示例

    Python實(shí)現(xiàn)字符串與數(shù)組相互轉(zhuǎn)換功能示例

    這篇文章主要介紹了Python實(shí)現(xiàn)字符串與數(shù)組相互轉(zhuǎn)換功能,結(jié)合具體實(shí)例形式分析了Python字符串與數(shù)組相關(guān)轉(zhuǎn)換功能的相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下
    2017-09-09
  • python根據(jù)日期返回星期幾的方法

    python根據(jù)日期返回星期幾的方法

    這篇文章主要介紹了python根據(jù)日期返回星期幾的方法,涉及Python針對(duì)日期模塊的相關(guān)使用技巧,需要的朋友可以參考下
    2015-07-07
  • Python實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列結(jié)構(gòu)的方法詳解

    Python實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列結(jié)構(gòu)的方法詳解

    優(yōu)先級(jí)隊(duì)列(priority queue)是0個(gè)或多個(gè)元素的集合,每個(gè)元素都有一個(gè)優(yōu)先權(quán),接下來(lái)就來(lái)看一下簡(jiǎn)潔的Python實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列結(jié)構(gòu)的方法詳解:
    2016-06-06
  • 淺析Python中g(shù)etattr和getattribute的調(diào)用

    淺析Python中g(shù)etattr和getattribute的調(diào)用

    在Python中,getattr和getattribute是兩個(gè)用于屬性訪問(wèn)的重要函數(shù),它們可以在運(yùn)行時(shí)動(dòng)態(tài)地獲取對(duì)象的屬性或自定義屬性訪問(wèn)行為,下面我們就來(lái)學(xué)習(xí)一下它們的具體用法吧
    2023-11-11
  • selenium+python自動(dòng)化測(cè)試之頁(yè)面元素定位

    selenium+python自動(dòng)化測(cè)試之頁(yè)面元素定位

    這篇文章主要介紹了selenium+python自動(dòng)化測(cè)試之頁(yè)面元素定位,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • 20行Python代碼實(shí)現(xiàn)一款永久免費(fèi)PDF編輯工具的實(shí)現(xiàn)

    20行Python代碼實(shí)現(xiàn)一款永久免費(fèi)PDF編輯工具的實(shí)現(xiàn)

    這篇文章主要介紹了20行Python代碼實(shí)現(xiàn)一款永久免費(fèi)PDF編輯工具的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 一百行python代碼將圖片轉(zhuǎn)成字符畫(huà)

    一百行python代碼將圖片轉(zhuǎn)成字符畫(huà)

    這篇文章主要為大家詳細(xì)介紹了一百行python代碼將圖片轉(zhuǎn)成字符畫(huà) ,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • python讀取xml文件方法解析

    python讀取xml文件方法解析

    這篇文章主要介紹了python讀取xml文件方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08

最新評(píng)論

开平市| 嘉峪关市| 乌海市| 宣城市| 宁远县| 通化县| 富民县| 柘城县| 德钦县| 阳信县| 即墨市| 乃东县| 西盟| 灌南县| 资阳市| 柯坪县| 东阿县| 佛山市| 西平县| 合肥市| 焦作市| 长沙市| 乌兰察布市| 离岛区| 富阳市| 昌平区| 始兴县| 黑龙江省| 应城市| 葵青区| 德庆县| 喀喇| 红原县| 海盐县| 绵竹市| 南靖县| 阿勒泰市| 湘潭县| 内丘县| 松阳县| 府谷县|