一個爬取人人網(wǎng)相冊的小腳本

背景與目標

從人人網(wǎng)爬取所有的圖片喊崖。

主要思路與邏輯

總體思路

首先我們注意到蒋荚,要爬取所有用戶相冊的圖片,我們首先要注意到人人所有的用戶ID都是九位數(shù)筹陵。比如這個相冊網(wǎng)址
http://photo.renren.com/photo/965740423/albumlist/v7?offset=0&limit=40#

嘗試去掉 “ 沿后?”后面的參數(shù),我們會發(fā)現(xiàn)這個相冊網(wǎng)址是結(jié)構(gòu)性的朽砰,可以被拆解為 http://photo.renren.com/photo/ + user_id +/albumlist/v7尖滚。

一個簡單而直接的想法是,我們將user_id從1遍歷到999999999,訪問每個ID的相冊瞧柔,如果為空漆弄,跳過;否則爬取這個相冊造锅。

但是撼唾,顯然,人人網(wǎng)的有效用戶數(shù)應該是不足一億的哥蔚,也就是說我們的命中率不超過百分之十倒谷,我們會爬取許多的無效頁面蛛蒙,這在效率上是一種拖累。

換一個思路渤愁,我們從某一個源用戶出發(fā)牵祟,訪問他關(guān)注的用戶,或者關(guān)注他的用戶抖格,然后再訪問他關(guān)注的那些用戶所關(guān)注的用戶诺苹,最后我們會遍歷到所有人人網(wǎng)有人關(guān)注的用戶(當然,也會漏掉所有無人關(guān)注的用戶雹拄,不過收奔,我相信這種邊緣性用戶是非常少的,可以忽略)滓玖。得到用戶的user_id之后坪哄,我們就可以訪問他的相冊,得到每個相冊的url呢撞,再訪問每個相冊的url损姜,拿到每張照片的url,最后訪問每張照片的url并保存在本地殊霞。

獲取每個用戶的所有相冊url

鑒于我們本機的硬盤容量和測試需要摧阅,我們顯然不能一開始就拿到所有的user_id,必須給它設(shè)置一個終止條件绷蹲,我們在同級目錄下創(chuàng)建一個photos文件夾棒卷,文件夾下創(chuàng)建一個id.txt,將初始的源用戶ID寫在里面祝钢,每次我們終止這個程序的時候比规,將剩余的未爬的user_id覆蓋性寫入這個txt,下一次開始的時候,再從這個txt讀取一開始的user_id列表拦英。這樣就相當于一次變相的斷點續(xù)爬蜒什。

對于每個user_id,構(gòu)造出他的相冊頁面,并爬取出所有的album_id疤估,構(gòu)造album_url添加進album_list

代碼如下:

#獲取關(guān)注的用戶的urls
def get_urls(base_user_id,users):

    browser = webdriver.PhantomJS()
    urls =[]
    base_url = 'http://www.renren.com/SysHome.do'   #登錄頁
    browser.get(base_url)
    browser.find_element_by_id('email').clear()     
    browser.find_element_by_id('password').clear()
    browser.find_element_by_id('email').send_keys('13689024414')
    browser.find_element_by_id('password').send_keys('19950708')
    browser.find_element_by_id('login').click()
    time.sleep(1)                                   #請勿注釋掉這一句
    user_ids=['%s' %base_user_id]
    print('OK1')

    count=0             #count是用來控制循環(huán)的灾常,否則user_ids不斷被append進urls,會直到爬取完所有的人人頁面才終止程序

    for user_id in user_ids:
        user_id = user_ids.pop(0)
        user_url = 'http://follow.renren.com/list/' + user_id + '/pub/v7'       #訪問每個用戶的關(guān)注用戶頁
        browser.get(user_url)
        followers = browser.find_elements_by_class_name('photo')            #用選擇器找到每個關(guān)注者

        for follower in followers:
            follower_id = follower.get_attribute('namecard')                #拿到關(guān)注者的user_id
            print(follower_id)
            if follower_id not in user_ids:
                user_ids.append(follower_id)                                #簡單的去重,加入user_ids列表
            
            url='http://photo.renren.com/photo/' + '%s' % follower_id + '/albumlist/v7?offset=0&limit=40#'  #構(gòu)造關(guān)注者的相冊url
            if url not in urls:
                urls.append(url)
                print(url)

        count += 1
    
        if count==users:
            
            break

    #print('OK2')

    if user_ids:
        print(user_ids[-1])
        with open('photos/last_id.txt','w+',encoding='utf-8') as f:     #循環(huán)結(jié)束后铃拇,將user_ids里面最后一個user_id寫入本地文件
            f.write(user_ids[-1])
        with open('photos/time.txt','a+',encoding='utf-8') as f:        #記錄運行時間和每次保存的最后一個user_id
            f.write(user_ids[-1]+'\n')

    #print(urls)

    browser.quit()
    return urls

爬取相冊中每個照片的url

人人網(wǎng)的相冊的照片是動態(tài)加載的钞瀑,用的是AJAX。雖然用Selenium+PhantomJS模擬也能看到慷荔,但遠不如直接訪問AJAX來得快雕什。打開Chrome,訪問某個照片數(shù)量大于40的相冊頁面,打開開發(fā)者工具-network-篩選XHR贷岸,頁面下拉壹士,可以看到形如http://photo.renren.com/photo/341508340/album-622844419/bypage/ajax/v7?page=3&pageSize=20的請求,這同樣是由user_id和album_id構(gòu)造出來的json url凰盔,直接讀取解析即可墓卦。
代碼如下:

#獲取照片的url
def get_photo_urls(base_user_id,users):
    browser = webdriver.PhantomJS()
    urls = get_urls(base_user_id,users)                 #調(diào)用get_urls獲取urls
    album_urls = []
    base_url = 'http://www.renren.com/SysHome.do'
    browser.get(base_url)
    browser.find_element_by_id('email').clear()
    browser.find_element_by_id('password').clear()
    browser.find_element_by_id('email').send_keys('xxxxxxxxxx') #請輸入你自己的手機號
    browser.find_element_by_id('password').send_keys('xxxxxxx') #請輸入你自己的密碼
    browser.find_element_by_id('login').click()
    time.sleep(1)

    print('OK3')

    #print(browser.get_cookies())
    for url in urls:
        print(url)
        browser.get(url)
        browser.implicitly_wait(3)
        albums=browser.find_elements_by_class_name("album-box")             #找到相冊
        
        for album in albums:
            if not album:
                continue
            album_url = album.find_element_by_class_name('album-item').get_attribute('href')
            count = album.find_element_by_class_name('album-count').text
            item={}
            print(album_url,count)
            item['url'] = album_url             
            item['count'] = int(count)
            album_urls.append(item)                     #album_urls是dict的list,包含每個相冊的url和相冊中照片的數(shù)量count

    photo_urls=[]                                       
    for item in album_urls:    #http://photo.renren.com/photo/500999244/album-848184418/v7?page=3&pageSize=20
        for i in range(1,math.ceil(item['count']/20)+1):
        
            browser.get(item['url']+'?page=%d&pageSize=20'%i)       #訪問照片數(shù)據(jù)來源的url
            
            browser.implicitly_wait(3)
            photos=browser.find_elements_by_class_name("photo-box") 
            for photo in photos:
                photo_url = photo.find_element_by_class_name('p-b-item').get_attribute('src')
                print(photo_url)
                photo_urls.append(photo_url)

    browser.quit()

    return photo_urls

下載到本地

我們在這個程序的同級目錄下建立一個photos文件夾,用于保存下載到本地的圖片户敬,并按下載日期分類落剪。
代碼如下:

#創(chuàng)建路徑
def make_dir(path):
    if not os.path.exists(path):
        os.mkdir(path)
    return None

#圖片保存到本地
def save_photos(urls):
    date = datetime.now()
    dir_name = date.strftime('%b %d')
    make_dir('photos/'+dir_name)
    n=1
    for url in urls:
        if not url:
            continue
        print(url)
        name = url[8:].replace('/','_')

        file_name = '%s' % name
        with open('photos/'+dir_name+'/'+file_name,'wb+') as f:             #請確保在本腳本的同級目錄下有一個photos文件夾,下載的圖片將會存儲在photos下按日期建立的文件夾中
            f.write(requests.get(url,headers=header(url)).content)
        print('正在下載第%s張圖片' % n)
        n = n+1
    print('OK4')
    return n            

構(gòu)造header

事實上尿庐,通過前幾步下載到本地的圖片忠怖,我們會發(fā)現(xiàn)還是無法打開,原因是因為我們用來請求的header是python自帶的header,網(wǎng)站不會給這種header返回正確的內(nèi)容抄瑟。所以我們要把自己偽裝成一個正常的用戶凡泣,代碼如下

def header(referer):
    headers = {
        'Host': 'fmn.rrimg.com',
        'Pragma': 'no-cache',
        'Accept-Encoding': 'gzip, deflate',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/59.0.3071.115 Safari/537.36',
        'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
        'Referer': '{}'.format(referer),
    }
    return headers

主程序main

def main():
    start_time=datetime.now()                       
    start_date=start_time.strftime('%b''%d')
    with open('photos/last_id.txt','r') as f:
        base_user_id=f.read()                       #獲取初始id,每次運行本程序之后此ID在get_urls()中更新
    users = 2                                       #獲取循環(huán)次數(shù)
    photo_urls = get_photo_urls(base_user_id,users)
    num = save_photos(photo_urls)
    end_time=datetime.now()
    do_time = (end_time - start_time).seconds
    with open('photos/time.txt','a+',encoding='utf-8') as f:
        f.write("{} 爬取了 {} 張圖片,耗時{}秒\n".format(start_date,num,do_time))

需要注意的地方

  1. users設(shè)置為2,主要是為了測試方便皮假,你也可以設(shè)置為10鞋拟,或者直接將循環(huán)條件改寫成更可控的模式。
  2. 我沒有寫去重惹资,在每次結(jié)束程序的時候贺纲,也只把待爬取里的user_id中最后那一個寫入本地文件。事實上更妥善的方法是褪测,連接數(shù)據(jù)庫猴誊,建一個seen表存已經(jīng)爬過的user_id,每次執(zhí)行程序前侮措,將seen中所有的user_id取出來構(gòu)造成一個seen集合懈叹,每次拿到新的user_id時,先判斷這個user_id是否在seen集合里分扎,如果不在澄成,爬取,并將其加入seen集合畏吓,再建一個表toParse存取每次結(jié)束時待爬的user_id,每次執(zhí)行前去讀取這個表环揽,每次執(zhí)行完覆蓋掉這個表。
  3. 我們構(gòu)造的header對于大部分圖片來說都已經(jīng)夠用庵佣,但是對于人人網(wǎng)早期的一些圖片仍然下載不了。某些圖片無法爬取的原因是汛兜,它的源已不可知巴粪,顯示成一張破裂圖片的式樣;但是有些圖片無法爬取的原因是因為我們構(gòu)造的header中的host是有問題的,這需要對圖片的url分情況處理(在此不再細述)

缺陷

  1. 盡管代碼里考慮到了一些意外情況肛根,但是仍然要說容錯率(也許應該說健壯性辫塌?)不夠,比如說派哲,最后一個user_id沒有關(guān)注的人怎么辦臼氨,訪問的相冊需要密碼怎么辦。
  2. phantomJS的效率并不高芭届,因為PhantomJS的本質(zhì)是一個無頭瀏覽器储矩,渲染本身就需要許多時間。
  3. 多線程多進程和異步的問題

總而言之褂乍,是時候上一個框架了持隧。

github地址

https://github.com/baoshuxie/renrenscript

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逃片,隨后出現(xiàn)的幾起案子屡拨,更是在濱河造成了極大的恐慌,老刑警劉巖褥实,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呀狼,死亡現(xiàn)場離奇詭異,居然都是意外死亡损离,警方通過查閱死者的電腦和手機哥艇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來草冈,“玉大人她奥,你說我怎么就攤上這事≡趵猓” “怎么了哩俭?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拳恋。 經(jīng)常有香客問我凡资,道長,這世上最難降的妖魔是什么谬运? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任隙赁,我火速辦了婚禮,結(jié)果婚禮上梆暖,老公的妹妹穿的比我還像新娘伞访。我一直安慰自己,他們只是感情好轰驳,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布厚掷。 她就那樣靜靜地躺著弟灼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冒黑。 梳的紋絲不亂的頭發(fā)上田绑,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音抡爹,去河邊找鬼掩驱。 笑死,一個胖子當著我的面吹牛冬竟,可吹牛的內(nèi)容都是我干的欧穴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼诱咏,長吁一口氣:“原來是場噩夢啊……” “哼苔可!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袋狞,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤焚辅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苟鸯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體同蜻,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年早处,在試婚紗的時候發(fā)現(xiàn)自己被綠了湾蔓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡砌梆,死狀恐怖默责,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咸包,我是刑警寧澤桃序,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站烂瘫,受9級特大地震影響媒熊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坟比,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一芦鳍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧葛账,春花似錦柠衅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魂贬。三九已至,卻和暖如春裙顽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宣谈。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工愈犹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闻丑。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓漩怎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嗦嗡。 傳聞我的和親對象是個殘疾皇子勋锤,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)侥祭,斷路器叁执,智...
    卡卡羅2017閱讀 134,711評論 18 139
  • 用到的組件 1、通過CocoaPods安裝 2矮冬、第三方類庫安裝 3谈宛、第三方服務 友盟社會化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 14,626評論 1 180
  • 心中有座城,藏著未亡人胎署。人去情別離吆录,心中空余城。 靈魂轉(zhuǎn)世投胎前琼牧,倘若眷戀太深恢筝,可帶上前世的一截骨頭,這樣在來生巨坊,...
    神奇小逗閱讀 1,115評論 0 0
  • 有幾個原因: 1.不管是什么情況撬槽,我都不會做小三的。我撤了抱究。 2.他也沒有做錯什么恢氯。他一直有講說,這是唯一能為我做...
    學霸小朋友閱讀 256評論 0 0
  • 《普通人的財富秘籍》第6篇 只考慮一個維度是不夠的鼓寺,我們引入“能力”維度來加深對錢的認識勋拟。 一個重要的思維工具“四...
    丁華秋實閱讀 986評論 0 4