Python 爬蟲獲取某貼吧所有成員用戶名

最近想用Python爬蟲搞搞百度貼吧的操作破讨,所以我得把原來(lái)申請(qǐng)的小號(hào)找出來(lái)用慨绳。有一個(gè)小號(hào)我忘了具體ID褒纲,只記得其中幾個(gè)字母以及某個(gè)加入的貼吧汤锨。所以今天就用爬蟲來(lái)獲取C語(yǔ)言貼吧的所有成員办铡。

計(jì)劃很簡(jiǎn)單辞做,爬百度貼吧的會(huì)員頁(yè)面琳要,把結(jié)果存到MySQL數(shù)據(jù)庫(kù)中,等到所有會(huì)員都爬完之后秤茅。我就可以使用簡(jiǎn)單的SQL語(yǔ)句查詢賬號(hào)名了稚补。由于C語(yǔ)言貼吧會(huì)員有50多萬(wàn),所以我還需要在合適的時(shí)候(例如插入數(shù)據(jù)庫(kù)失斏┥ )把錯(cuò)誤信息打印到日志文件中孔厉。由于我是Python新手,所以就不弄什么多線程得了帖努,直接一個(gè)腳本用到黑撰豺。

看著很簡(jiǎn)單,實(shí)際也很簡(jiǎn)單拼余。寫完了我看了一下污桦,用到的知識(shí)只有最基礎(chǔ)的SQL操作、BeautifulSoup解析匙监。

首先第一步就是看一下這個(gè)吧的信息頁(yè)有多少頁(yè)凡橱,關(guān)鍵代碼如下。踩了兩天坑亭姥,總算感覺(jué)對(duì)BeautifulSoup熟悉了一點(diǎn)稼钩。代碼也很簡(jiǎn)單,按照class名查找到總頁(yè)數(shù)這個(gè)標(biāo)簽达罗,然后用正則表達(dá)式匹配到頁(yè)數(shù)數(shù)字坝撑。這里要說(shuō)一下,正則表達(dá)式的分組真好用粮揉。以前偷懶只學(xué)了一點(diǎn)正則表達(dá)式巡李,發(fā)現(xiàn)沒(méi)啥作用,只有配合分組才能比較精確的查找字符扶认。

    html = request.urlopen(base_url).read().decode(encoding)
    soup = BeautifulSoup(html, 'lxml')
    page_span = soup.find('span', class_='tbui_total_page')
    p = re.compile(r'共(\d+)頁(yè)')
    result = p.match(page_span.string)
    global total_pages
    total_pages = int(result.group(1))

    logger.info(f'會(huì)員共{total_pages}頁(yè)')

有了總頁(yè)數(shù)侨拦,我們就可以遍歷頁(yè)面了,代碼如下辐宾。寫的雖然比較臟狱从,但是能用就行了,大家嫌難看就難看吧叠纹。這里做的事情就很簡(jiǎn)單了矫夯,從第一頁(yè)開(kāi)始遍歷,一直遍歷到最后一頁(yè)吊洼。把每一頁(yè)的用戶名字提取出來(lái),然后用_insert_table(connection, name)函數(shù)存到MySQL中制肮。

因?yàn)槲覟榱耸∈旅扒希苯影寻俣扔脩裘?dāng)做主鍵了递沪。但是保不齊貼吧有什么bug,導(dǎo)致用戶名重復(fù)之類的問(wèn)題综液,導(dǎo)致插入失敗款慨。所以我用try把保存這一塊包起來(lái)。有異常的話就打印到日志中谬莹,方便排查檩奠。日志分成兩種級(jí)別的,INFO級(jí)別輸出到控制臺(tái)附帽,ERROR級(jí)別輸出到文件埠戳。

def _find_all_users():
    global connection
    for i in range(start_page, total_pages + 1):
        target_url = f'{base_url}&pn={i}'
        logger.info(f'正在分析第{i}頁(yè)')
        html = request.urlopen(target_url).read().decode(encoding)
        soup = BeautifulSoup(html, 'lxml')
        outer_div = soup.find('div', class_='forum_info_section member_wrap clearfix bawu-info')
        inner_spans = outer_div.find_all('span', class_='member')
        for index, span in enumerate(inner_spans):
            name_link = span.find('a', class_='user_name')
            name = name_link.string
            logger.info(f'已找到 {name}')

            try:
                _insert_table(connection, name)
            except:
                logger.error(f'第{i}頁(yè){index}第個(gè)用戶 {name} 發(fā)生異常')

完整的代碼見(jiàn)下。

"""
Python寫的百度貼吧工具
"""
import pymysql

host = 'localhost'
db_name = 'tieba'
username = 'root'
password = '12345678'


def _get_connection(host, username, password, db_name):
    return pymysql.connect(host=host,
                           user=username,
                           password=password,
                           charset='utf8mb4',
                           db=db_name)


def _create_table(connection):
    create_table_sql = """
    CREATE TABLE tieba_member(
    username CHAR(255) PRIMARY KEY 
    )
    """
    with connection.cursor() as cursor:
        cursor.execute(create_table_sql)
        connection.commit()


def _insert_table(connection, username):
    insert_table_sql = """
    INSERT INTO tieba_member 
    VALUES(%s)"""

    with connection.cursor() as cursor:
        cursor.execute(insert_table_sql, (username,))
        connection.commit()


import urllib.request as request
from bs4 import BeautifulSoup
import re
import tieba.log_config
import logging

logger = logging.getLogger()

encoding = 'GBK'

base_url = 'http://tieba.baidu.com/bawu2/platform/listMemberInfo?word=c%D3%EF%D1%D4'
# base_url = 'http://tieba.baidu.com/bawu2/platform/listMemberInfo?word=%B9%FD%C1%CB%BC%B4%CA%C7%BF%CD'
start_page = 1
total_pages = None

connection = _get_connection(host, username, password, db_name)


def _get_total_pages():
    html = request.urlopen(base_url).read().decode(encoding)
    soup = BeautifulSoup(html, 'lxml')
    page_span = soup.find('span', class_='tbui_total_page')
    p = re.compile(r'共(\d+)頁(yè)')
    result = p.match(page_span.string)
    global total_pages
    total_pages = int(result.group(1))

    logger.info(f'會(huì)員共{total_pages}頁(yè)')


def _find_all_users():
    global connection
    for i in range(start_page, total_pages + 1):
        target_url = f'{base_url}&pn={i}'
        logger.info(f'正在分析第{i}頁(yè)')
        html = request.urlopen(target_url).read().decode(encoding)
        soup = BeautifulSoup(html, 'lxml')
        outer_div = soup.find('div', class_='forum_info_section member_wrap clearfix bawu-info')
        inner_spans = outer_div.find_all('span', class_='member')
        for index, span in enumerate(inner_spans):
            name_link = span.find('a', class_='user_name')
            name = name_link.string
            logger.info(f'已找到 {name}')

            try:
                _insert_table(connection, name)
            except:
                logger.error(f'第{i}頁(yè){index}第個(gè)用戶 {name} 發(fā)生異常')


import datetime

if __name__ == '__main__':
    _get_total_pages()
    _find_all_users()

還有另一個(gè)文件用來(lái)配置日志的蕉扮。你也可以把這兩個(gè)文件合在一起整胃,只不過(guò)看著可能更亂了。

import logging

# 創(chuàng)建Logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

# 創(chuàng)建Handler

# 終端Handler
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.DEBUG)

# 文件Handler
fileHandler = logging.FileHandler('log.log', mode='a', encoding='UTF-8')
fileHandler.setLevel(logging.ERROR)

# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
consoleHandler.setFormatter(formatter)
fileHandler.setFormatter(formatter)

# 添加到Logger中
logger.addHandler(consoleHandler)
logger.addHandler(fileHandler)


性能測(cè)試

當(dāng)然由于要爬的數(shù)據(jù)量比較大喳钟,我們還要計(jì)算一下可能的運(yùn)行時(shí)間屁使。首先不考慮爬蟲被百度封了的情況。我把代碼稍作修改奔则,設(shè)定只爬前100頁(yè)蛮寂。

import datetime

if __name__ == '__main__':
    # _get_total_pages()
    total_pages = 100
    time1 = datetime.datetime.today()

    _find_all_users()
    time2 = datetime.datetime.today()
    print(time2)
    print(time1)
    print(time2 - time1)

結(jié)果如下,用時(shí)將近兩分鐘易茬。做了簡(jiǎn)單計(jì)算得出結(jié)論酬蹋,要爬完c語(yǔ)言貼吧的52萬(wàn)個(gè)會(huì)員,需要將近7個(gè)小時(shí)疾呻。所以程序還需要改進(jìn)除嘹。

2017-04-04 23:57:59.197993
2017-04-04 23:56:10.064666
0:01:49.133327

首先先從數(shù)據(jù)庫(kù)方面考慮一下。Windows下MySQL默認(rèn)的數(shù)據(jù)庫(kù)引擎是Innodb岸蜗,特點(diǎn)是支持事務(wù)管理尉咕、外鍵、行級(jí)鎖璃岳,但是相應(yīng)的速度比較慢年缎。我把表重新建為MyISAM類型的。然后重新運(yùn)行一下測(cè)試铃慷,看看這次速度會(huì)不會(huì)有變化单芜。

CREATE TABLE tieba_member (
  username CHAR(255) PRIMARY KEY
)
  ENGINE = MyISAM

這次性能提升的有點(diǎn)快,速度足足提高了76%犁柜≈摒可見(jiàn)默認(rèn)的并不一定是最好的。

2017-04-05 00:15:19.989766
2017-04-05 00:14:53.407476
0:00:26.582290

既然都開(kāi)始測(cè)試了,不妨干脆點(diǎn)扒腕。MySQL還有一種引擎是Memory绢淀,直接把數(shù)據(jù)放到內(nèi)存中。速度肯定會(huì)更快瘾腰!不過(guò)測(cè)試結(jié)果很遺憾皆的,還是26秒√E瑁可見(jiàn)數(shù)據(jù)庫(kù)這方面的優(yōu)化到頭了费薄。

CREATE TABLE tieba_member (
  username CHAR(255) PRIMARY KEY
)
  ENGINE = MEMORY

不過(guò)性能確實(shí)提高了很多。經(jīng)過(guò)計(jì)算栖雾,這次只需要一個(gè)半小時(shí)即可爬完52萬(wàn)個(gè)用戶楞抡。如果在開(kāi)多個(gè)進(jìn)程,相信速度還會(huì)更快岩灭。所以這篇文章就差不多完成了拌倍。等明天爬完之后,我把結(jié)果更新一下噪径,任務(wù)就真正完成了柱恤!

不過(guò)結(jié)果很遺憾,爬蟲失敗了找爱。為了速度更快我開(kāi)了4個(gè)進(jìn)程梗顺,分別爬1-5000頁(yè),5001-10000頁(yè)车摄,10001-15000頁(yè)寺谤,以及15000-到最后4部分。
但是日志輸出顯示出現(xiàn)很多重復(fù)的用戶名吮播,5000頁(yè)之后的用戶名竟然和第一頁(yè)相同变屁。我百思不得其解,在使用瀏覽器測(cè)試發(fā)現(xiàn)意狠,不知道是百度的防爬蟲機(jī)制還是bug之類的粟关,瀏覽器只能顯示到450多頁(yè),在往后就會(huì)顯示為空頁(yè)面环戈,如果頁(yè)數(shù)更大闷板,就一直返回第一頁(yè)的內(nèi)容。因此依賴于這個(gè)頁(yè)面的貼吧爬蟲宣布失敗院塞。

雖然失敗了遮晚,但是還是學(xué)習(xí)到了不少經(jīng)驗(yàn)。我測(cè)試了一下爬前450頁(yè)拦止,僅用時(shí)44秒县遣。說(shuō)明爬蟲速度倒是還星還行。

import datetime
from multiprocessing import Process

if __name__ == '__main__':

    total_pages = _get_total_pages()

    processes = []
    processes.append(Process(target=_find_all_users, args=(1, 150)))
    processes.append(Process(target=_find_all_users, args=(151, 300)))
    processes.append(Process(target=_find_all_users, args=(301, 450)))

    time1 = datetime.datetime.today()
    for process in processes:
        process.start()

    for process in processes:
        process.join()

    time2 = datetime.datetime.today()
    print(f'開(kāi)始時(shí)間{time1}')
    print(f'結(jié)束時(shí)間{time2}')
    print(f'用時(shí){time2 - time1}')
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市艺玲,隨后出現(xiàn)的幾起案子括蝠,更是在濱河造成了極大的恐慌,老刑警劉巖饭聚,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異搁拙,居然都是意外死亡秒梳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門箕速,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酪碘,“玉大人,你說(shuō)我怎么就攤上這事盐茎⌒丝眩” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵字柠,是天一觀的道長(zhǎng)探越。 經(jīng)常有香客問(wèn)我,道長(zhǎng)窑业,這世上最難降的妖魔是什么钦幔? 我笑而不...
    開(kāi)封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮常柄,結(jié)果婚禮上鲤氢,老公的妹妹穿的比我還像新娘。我一直安慰自己西潘,他們只是感情好卷玉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著喷市,像睡著了一般相种。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上东抹,一...
    開(kāi)封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天蚂子,我揣著相機(jī)與錄音,去河邊找鬼缭黔。 笑死食茎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的馏谨。 我是一名探鬼主播别渔,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了哎媚?” 一聲冷哼從身側(cè)響起喇伯,我...
    開(kāi)封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拨与,沒(méi)想到半個(gè)月后稻据,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡买喧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年捻悯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淤毛。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡今缚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出低淡,到底是詐尸還是另有隱情姓言,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布蔗蹋,位于F島的核電站何荚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纸颜。R本人自食惡果不足惜兽泣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胁孙。 院中可真熱鬧唠倦,春花似錦、人聲如沸涮较。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狂票。三九已至候齿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闺属,已是汗流浹背慌盯。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掂器,地道東北人亚皂。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像国瓮,于是被迫代替她去往敵國(guó)和親灭必。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狞谱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1 前言 作為一名合格的數(shù)據(jù)分析師,其完整的技術(shù)知識(shí)體系必須貫穿數(shù)據(jù)獲取禁漓、數(shù)據(jù)存儲(chǔ)跟衅、數(shù)據(jù)提取、數(shù)據(jù)分析播歼、數(shù)據(jù)挖掘伶跷、...
    whenif閱讀 18,073評(píng)論 45 523
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,144評(píng)論 25 707
  • 爬蟲文章 in 簡(jiǎn)書程序員專題: like:128-Python 爬取落網(wǎng)音樂(lè) like:127-【圖文詳解】py...
    喜歡吃栗子閱讀 21,755評(píng)論 4 411
  • 概述 近來(lái)花了一些時(shí)間簡(jiǎn)單學(xué)習(xí)了一下python,簡(jiǎn)而言之:拋棄運(yùn)行效率因素不說(shuō)秘狞,從編碼的角度撩穿,其優(yōu)雅、簡(jiǎn)潔的語(yǔ)法...
    nmnethaha閱讀 906評(píng)論 6 4
  • 現(xiàn)在社區(qū)類產(chǎn)品已經(jīng)非常多了谒撼,不要輕易嘗試,如果嘗試那就一定要精準(zhǔn)垂直的找到一片暫時(shí)的藍(lán)海雾狈。 那么怎么做社區(qū)呢廓潜? 首...
    Mr丶Peng閱讀 273評(píng)論 0 0