爬蟲實戰(一):爬取微博用戶信息

前敘

系列文章:html

爬蟲實戰(一):爬取微博用戶信息node

爬蟲實戰(二):Selenium 模擬登陸並爬取信息python

爬蟲實戰(三):微博用戶信息分析mysql

該系列文章介紹了什麼?git

1.爬蟲分析和處理方法github

2.Python中的數據庫操做方法正則表達式

3.Selenium瀏覽器自動化以及無頭瀏覽器使用方法sql

4.對數據進行詞雲分析的方法數據庫

5.對數據進行可視化的方法瀏覽器

6.LDA隱含狄利克雷分佈模型的建模和使用方法

前言

最近作課設,是一個有關我的隱私安全的課題,在網上找了不少論文,最後上海交通大學的一篇碩士論文《面向社會工程學的SNS分析和挖掘》[1] 給了我不少靈感,由於是對我的隱私安全進行評估,因此咱們基於微博社交網絡獲取的數據進行分析。如下是該系列第一篇文章,記錄爬取微博用戶信息的過程。

先決條件

咱們此次的目標是爬取微博我的用戶的資料信息和動態信息並保存在 mysql 數據庫中。

由於爬取微博主頁 weibo.com/ 或者 m.weibo.cn/ 較爲困難,因此咱們爬取 weibo.cn,這是一個落後的塞班年代的網頁,沒有混淆等等一系列新技術,用戶動態等從html裏面就能夠獲取,爬取相對來講比較簡單。

首先要對網頁進行大體分析,獲取爬蟲的先決條件。

cookies

由於微博對訪客進行了限制,因此請求裏面沒有 cookies 的話動態沒法抓取徹底。

故咱們也須要獲取 cookie:

  • 用Chrome打開passport.weibo.cn/signin/logi…
  • 按F12鍵打開Chrome開發者工具;
  • 點開「Network」,將「Preserve log」選中,輸入微博的用戶名、密碼,登陸
  • 點擊Chrome開發者工具「Name"列表中的"m.weibo.cn",點擊"Headers",其中"Request Headers"下,"Cookie"後的值即爲咱們要找的cookie值,複製便可

UID

由於咱們是抓取用戶數據,因此首先應該知道如何獲取惟一標識符——uid,每一個用戶的 uid 是不同的,有了 uid 咱們才能夠經過它來標識用戶。登陸本身的帳戶之後,咱們能夠訪問用戶的資料頁(以張亞勤爲例),能夠看到頁面地址爲 https://weibo.cn/1645171780/info,其中 "1645171780" 即爲張亞勤的 uid,如圖所示:

n8Vrl.jpg

網頁分析

獲取了 uid 和 cookie ,咱們來對網頁進行詳細分析。

用戶資料頁源碼分析

由於資料頁數據量不多,分析和處理都比較容易,因此首先從這裏下手。

由上一張圖片能夠看出,資料頁分紅 **基本信息 ,學習經歷,工做經歷,其餘信息 ** 四個模塊,咱們只須要前三個模塊就能夠了。分析源碼的 html ,咱們發現 class="tip"> 恰好能夠標識四個信息模塊,而對於每一個模塊內部的資料條目,class="c" 能夠進行一一標識,如圖所示:

n8xVJ.jpg

使用正則表達式進行匹配,關於正則表達式的使用方法,請看個人另外一篇文章。代碼以下:

tip = re.compile(r'class="tip">(.*?)></div>', re.S) #匹配四個模塊全部內容
title = re.compile(r'(.*?)</div><div', re.S)  # 匹配基本信息/學習經歷/工做經歷/其餘信息
node = re.compile(r'.*?class="c"(.*?)$', re.S) # 匹配一個模塊中的全部內容
info = re.compile(r'>(.*?)<br/', re.S) # 匹配資料條
複製代碼

用戶動態頁源碼分析

對於一頁的動態來講很好分析,每一條動態內容前面都有 <span class="ctt">,而且一一對應。而動態發佈時間一一對應 <span class="ct"> ,如圖所示:

n8QzB.jpg

正則表達式代碼以下:

dynamic = re.compile(r'.*?><span class="ctt">(.*?)<a href', re.S)  # 匹配動態
times = re.compile(r'.*?<span class="ct">(.*?)&nbsp', re.S)  # 匹配動態發佈時間
複製代碼

能夠從第一頁中獲取頁數:

page_number = re.compile(r'.*/(\d*?)頁</div>', re.S)  # 匹配動態頁數
複製代碼

爬取信息

有了前面的鋪墊,爬取用戶資料便比較容易實現了。

對於用戶資料,使用前面的正則表達式對爬去的頁面進行處理,有如下代碼:

tip = re.compile(r'class="tip">(.*?)></div>', re.S) #匹配四個模塊全部內容
title = re.compile(r'(.*?)</div><div', re.S)  # 匹配基本信息/學習經歷/工做經歷/其餘信息
node = re.compile(r'.*?class="c"(.*?)$', re.S) # 匹配一個模塊中的全部內容
info = re.compile(r'>(.*?)<br/', re.S) # 匹配資料條
Uname = ''
Certified = ''
Sex = ''
Relationship = ''
Area = ''
Birthday = ''
Education_info = ''
Work_info = ''
Description = ''
for one in tips:
    titleone = re.findall(title, one)  # 信息標題

    node_tmp = re.findall(node, one)
    infos = re.findall(info, node_tmp[0])  # 信息
    if (titleone[0] == '基本信息'):
        for inf in infos:
            if (inf.startswith('暱稱')):
                _, Uname = inf.split(':', 1)
            elif (inf.startswith('認證信息')):
                print(inf)
                _, Certified = inf.split(':', 1)
            elif (inf.startswith('性別')):
                _, Sex = inf.split(':', 1)
            elif (inf.startswith('感情情況')):
                _, Relationship = inf.split(':', 1)
            elif (inf.startswith('地區')):
                _, Area = inf.split(':', 1)
            elif (inf.startswith('生日')):
                _, Birthday = inf.split(':', 1)
            elif (inf.startswith('簡介')):
                print(inf.split(':'))
                _, Description = inf.split(':', 1)
            else:
                pass
    elif (titleone[0] == '學習經歷'):
        for inf in infos:
            Education_info += inf.strip('·').replace("&nbsp", '') + " "
    elif (titleone[0] == '工做經歷'):
        for inf in infos:
            Work_info += inf.strip('·').replace("&nbsp", '') + " "
    else:
        pass
複製代碼

而對於用戶動態信息,處理的代碼:

dynamic = re.compile(r'.*?><span class="ctt">(.*?)<a href', re.S)  # 匹配動態
times = re.compile(r'.*?<span class="ct">(.*?)&nbsp', re.S)  # 匹配動態發佈時間
page_number = re.compile(r'.*/(\d*?)頁</div>', re.S)  # 匹配動態頁數
dys = re.findall(dynamic, res.text)
ts = re.findall(times, res.text)
pages = re.findall(page_number, res.text)
pagenums = pages[0]

mainurl = url
label = 0  # 標籤用於計數,每5~20次延時10S
tag = random.randint(5, 20)
for pagenum in range(int(pagenums))[1:]:
    if (label == tag):
        time.sleep(10)
        label = 0
        tag = random.randint(5, 20)
    # 隨機選擇,防止被ban
    cookie = random.choice(cookies)
    cookie = getcookies(cookie)
    headers = {
        'User_Agent': random.choice(user_agents)
    }
    pagenum += 1
    label += 1
    url = mainurl + '?page=' + str(pagenum)#更改頁數
    page = gethtml(url, headers, cookie, conf, use_proxies)
    dys += re.findall(dynamic, page.text)
    ts += re.findall(times, page.text)
dys = dys[1:]
複製代碼

至此爬蟲這部分代碼基本上完成。

保存數據到數據庫

若是沒有保存在數據庫的須要,能夠不用閱讀該部分。

原本以前是使用 pymysql + SQL語句實現數據庫操做,可是這樣太繁瑣了,而且這些訪問數據庫的代碼若是分散到各個函數中,勢必沒法維護,也不利於代碼複用。因此在這裏我使用ORM框架(SQLAlchemy)來操做數據庫,該框架實現了對數據庫的映射操做,即封裝了數據庫操做,簡化代碼邏輯。

首先建立三個表:

# 微博用戶信息表
    wb_user = Table('wb_user', metadata,
                    Column('user_ID', Integer, primary_key=True, autoincrement=True),  # 主鍵,自動添加
                    Column("uid", String(20), unique=True, nullable=False),  # 微博用戶的uid
                    Column("Uname", String(50), nullable=False),  # 暱稱
                    Column("Certified", String(50), default='', server_default=''),  # 認證信息
                    Column("Sex", String(200), default='', server_default=''),  # 性別nullable=False
                    Column("Relationship", String(20), default='', server_default=''),  # 感情情況
                    Column("Area", String(500), default='', server_default=''),  # 地區
                    Column("Birthday", String(50), default='', server_default=''),  # 生日
                    Column("Education_info", String(300), default='', server_default=''),  # 學習經歷
                    Column("Work_info", String(300), default='', server_default=''),  # 工做經歷
                    Column("Description", String(2500), default='', server_default=''),  # 簡介
                    mysql_charset='utf8mb4'
                    )

    # 微博用戶動態表
    wb_data = Table('wb_data', metadata,
                    Column('data_ID', Integer, primary_key=True, autoincrement=True),  # 主鍵,自動添加
                    Column('uid', String(20), ForeignKey(wb_user.c.uid), nullable=False),  # 外鍵
                    Column('weibo_cont', TEXT, default=''),  # 微博內容
                    Column('create_time', String(200), unique=True),  # 建立時間,unique用來執行upsert操做,判斷衝突
                    mysql_charset='utf8mb4'
                    )

    # 動態主題表
    wb_topic = Table('wb_topic', metadata,
                     Column('topic_ID', Integer, primary_key=True, autoincrement=True),  # 主鍵,自動添加
                     Column('uid', String(20), ForeignKey(wb_user.c.uid), nullable=False),  # 外鍵
                     Column('topic', Integer, nullable=False),  # 主題-----默認5類
                     Column('topic_cont', String(20), nullable=False, unique=True),  # 主題內容
                     mysql_charset='utf8mb4'
                     )
複製代碼

這裏有一個細節須要注意,那就是 mysql 的編碼使用了utf8m64的編碼方式,爲何要使用這種方式呢?由於微博裏面的emoji 表情佔4個字節,超過了utf-8 編碼範圍:UTF-8 是 3 個字節,其中已經包括咱們平常能見過的絕大多數字體,但 3 個字節遠遠不夠容納全部的文字, 因此便有了utf8mb4 , utf8mb4 是 utf8 的超集,佔4個字節, 向下兼容utf8。使用 utf8mb4 要求:

MySQL數據庫版本>=5.5.3

MySQL-python 版本 >= 1.2.5

而後咱們將爬蟲獲取的信息存到數據庫中,首先是資料頁數據:

from sqlalchemy import MetaData, Table
from sqlalchemy.dialects.mysql import insert
ins = insert(table).values(uid=uid, Uname=Uname, Certified=Certified, Sex=Sex, Relationship=Relationship,Area=Area,Birthday=Birthday,Education_info=Education_info,Work_info=Work_info,Description=Description)
ins = ins.on_duplicate_key_update(
# 若是不存在則插入,存在則更新(upsert操做#http://docs.sqlalchemy.org/en/latest/dialects/mysql.html#mysql-insert-on-duplicate-key-#update)
    Uname=Uname, Certified=Certified, Sex=Sex, Relationship=Relationship, Area=Area,
    Birthday=Birthday, Education_info=Education_info, Work_info=Work_info, Description=Description
)
conn.execute(ins)
複製代碼

接着是動態數據保存在數據庫中:

re_nbsp = re.compile(r'&nbsp', re.S)  # 去除$nbsp
re_html = re.compile(r'</?\w+[^>]*>', re.S)  # 去除html標籤
re_200b = re.compile(r'\u200b', re.S)  # 去除分隔符
re_quot = re.compile(r'&quot', re.S)
for i in range(len(ts)):#len(ts)爲動態數
	#去除噪聲
    dys[i] = re_nbsp.sub('', dys[i])
    dys[i] = re_html.sub('', dys[i])
    dys[i] = re_200b.sub('', dys[i])
    dys[i] = re_quot.sub('', dys[i])
    ins = insert(table).values(uid=uid, weibo_cont=pymysql.escape_string(dys[i]), create_time=ts[i])
    ins = ins.on_duplicate_key_update(weibo_cont=pymysql.escape_string(dys[i]))
    conn.execute(ins)
複製代碼

尾聲

整個爬蟲的數據獲取部分已經基本上介紹完畢,完整代碼見 github.com/starFalll/S… .

下一篇介紹一下對獲取的數據進行處理的過程。

參考:[1]陸飛.面向社會工程學的SNS分析和挖掘[D].上海:上海交通大學,2013.

相關文章
相關標籤/搜索