1. 程式人生 > >最新python爬蟲抓取新浪微博千萬級資料,scrapy思路+架構+原始碼

最新python爬蟲抓取新浪微博千萬級資料,scrapy思路+架構+原始碼

1.1 爬取目標

爬取的目標是新浪微博使用者的公開基本資訊,如使用者暱稱、頭像、使用者的關注、粉絲列表以及釋出的微博等

1.2 準備工作

    代理池、 Cookies 池已經實現並可以正常執行,安裝 Scrapy 、PyMongo 庫

1.3 爬取思路

    首先我們要實現使用者的大規模爬取。 這裡採用的爬取方式是,以微博的幾個大 V 為起始點,爬取他們各內的粉絲和關注列表,然後獲取粉絲和關注列表的粉絲和關注列表,以此類推,這樣下去就可以實現遞迴爬取。 如果一個使用者與其他使用者有社交網路上的關聯,那他們的資訊就會被爬蟲抓取到,這樣我們就可以做到對所有使用者的爬取 。 通過這種方式,我們可以得到使用者的唯一 ID ,再根據 ID 獲取每個使用者釋出的微博即可。

1.4 爬取分析

    爬取的站點是https://m.weibo.cn,此站點是微博移動端的站點,找到一個使用者的主頁面

數智實驗室

在頁面最上方可以看到她的關注和粉絲數量 。 我們點選關注,進入到她的關注列表

開啟開發者工具,切換到XHR 過濾器,一直下拉關注列表,即可看到下方會出現很多人ajax請求,這些請求就是獲取關注列表的 Ajax 請求.

  1. AJAX = 非同步 JavaScript 和 XML。
  2. AJAX 是一種用於建立快速動態網頁的技術。
  3. 通過在後臺與伺服器進行少量資料交換,AJAX 可以使網頁實現非同步更新。這意味著可以在不重新載入整個網頁的情況下,對網頁的某部分進行更新。

開啟第一個 Ajax 請求, 將其展開之後 即可看到其關注的使用者的基本資訊。接下來我們只需要構造這個請求的引數。其中最主要的引數就是 cont ainerid 和 page 。 有了這兩個引數,我們同樣可以獲取請求結果 。 我們可以將介面精簡為 :

這裡的 containerid 的前半部分是固定的,後半部分是使用者的 id 。 所以這裡引數就可以構造出來了,只需要修改 containerid 最後的 id 和 page 引數即可獲取分頁形式的關注列表資訊 。

利用同樣的方法,我們也可以分析使用者詳情的 Ajax 連結、使用者微博列表的 Ajax 連結,如下所示:

  1. #使用者詳情 API
user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&luicode=10000011&lfid=230413{uid}_-_WEIBO_SECOND_PROFILE_WEIBO&type=uid&value={uid}&containerid=100505{uid}'
  1. #關注列表 API
follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}'
  1. #粉絲列表 API
fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}'
  1. #微博列表API
weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&luicode=10000011&lfid=23041{uid}_-_WEIBO_SECOND_PROFILE_WEIBO&type=uid&value={uid}&containerid=100505{uid}'

此處的 uid 和 page 分別代表使用者 ID 和分頁頁碼。

2.1 新建專案

接下來,我們用 Scrapy 來實現這個抓取過程。 首先建立一個專案,命令如下所示:scrapy startproject weibo進入專案中,新建一個 Spider,名為 weibocn ,命令如下所示 :scrapy genspider weibocn m.weibo.cn我們首先修改 Spider,配置各個 Ajax 的 URL ,選取幾個大 V,將他們的 ID 賦值成一個列表,實現 start_requests()方法, 也就是依次抓取各個大 V 的個人詳情,然後用 parse_user()進行解析

# -*- coding: utf-8 -*-

import scrapy

from scrapy import Request

class WeibocnSpider(scrapy.Spider):

    name = 'weibocn'

    allowed_domains = ['m.weibo.cn']

    user_url ='https://m.weibo.cn/api/container/getIndex?uid={uid}&luicode=10000011&lfid=230413{uid}_-_WEIBO_SECOND_PROFILE_WEIBO&type=uid&value={uid}&containerid=100505{uid}'


    follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}'


    fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}'



    weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&luicode=10000011&lfid=23041{uid}_-_WEIBO_SECOND_PROFILE_WEIBO&type=uid&value={uid}&containerid=100505{uid}'



    start_users = ['3217179555', '1742566624', '2282991915', '1288739185', '3952070245', '5878659096']


    def start_requests(self):

        for uid in self.start_users:

            yield Request(self.user_url.format(uid=uid), callback=self.parse_user)

2.2 建立Item

    解析使用者的基本資訊並生成 Item。 這裡我們先定義幾個 Item ,如使用者、使用者關係、微博Item ,如下所示:

class UserItem(Item):

    collection = 'users'



    id = Field()

    name = Field()

    avatar = Field()

    cover = Field()

    gender = Field()

    description = Field()

    fans_count = Field()

    follows_count = Field()

    weibos_count = Field()

    verified = Field()

    verified_reason = Field()

    verified_type = Field()

    follows = Field()

    fans = Field()

    crawled_at = Field()





class UserRelationItem(Item):

    collection = 'users'



    id = Field()

    follows = Field()

    fans = Field()





class WeiboItem(Item):

    collection = 'weibos'



    id = Field()

    attitudes_count = Field()

    comments_count = Field()

    reposts_count = Field()

    picture = Field()

    pictures = Field()

    source = Field()

    text = Field()

    raw_text = Field()

    thumbnail = Field()

    user = Field()

    created_at = Field()

    crawled_at = Field()

這裡定義了 collection 欄位,指明儲存的 Collection 的名稱。 使用者的關注和粉絲列表直接定義為一個單獨的 UserRelationitem ,其中 id 就是使用者的 ID, follows 就是使用者關注列表, fans 是粉絲列表

2.3 提取資料

開始解析使用者的基本資訊,實現 parse_user ()方法,如下所示:

def parse_user(self, response):

    """

    解析使用者資訊

    :param response: Response物件

    """

    self.logger.debug(response)

    result = json.loads(response.text)

    if result.get('data').get('userInfo'):

        user_info = result.get('data').get('userInfo')

        user_item = UserItem()

        field_map = {

            'id': 'id', 'name': 'screen_name', 'avatar': 'profile_image_url', 'cover': 'cover_image_phone',

            'gender': 'gender', 'description': 'description', 'fans_count': 'followers_count',

            'follows_count': 'follow_count', 'weibos_count': 'statuses_count', 'verified': 'verified',

            'verified_reason': 'verified_reason', 'verified_type': 'verified_type'

        }

        for field, attr in field_map.items():

            user_item[field] = user_info.get(attr)

        yield user_item

        # 關注

        uid = user_info.get('id')

        yield Request(self.follow_url.format(uid=uid, page=1), callback=self.parse_follows,meta={'page': 1, 'uid': uid})

        # 粉絲

        yield Request(self.fan_url.format(uid=uid, page=1), callback=self.parse_fans,meta={'page': 1, 'uid': uid})

        # 微博

        yield Request(self.weibo_url.format(uid=uid, page=1), callback=self.parse_weibos,meta={'page': 1, 'uid': uid})

在這裡一共完成了兩個操作:

  1. 解析 JSON 提取使用者資訊並生成 UserItem 返回 。 我們並沒有採用常規的逐個賦值的方法,而是定義了一個欄位對映關係 。 我們定義的欄位名稱可能和 JSON 中使用者的欄位名稱不同,所以在這裡定義成一個字典,然後遍歷字典的每個欄位實現逐個欄位的賦值。
  2. 構造使用者的關注、粉絲、微博的第一頁的連結,井生成 Request ,這裡需要的引數只有使用者的ID 。 另外,初始分頁頁碼直接設定為 l 即可。

接下來,我們還需要儲存使用者的關注和粉絲列表。以關注列表為例,其解析方法為 parse_follows() ,實現如下所示:

def parse_follows(self, response):

    """

    解析使用者關注

    :param response: Response物件

    """

    result = json.loads(response.text)

    if result.get('ok') and result.get('data').get('cards') and len(result.get('data').get('cards')) and result.get('data').get('cards')[-1].get('card_group'):

        # 解析使用者

        follows = result.get('data').get('cards')[-1].get('card_group')

        for follow in follows:

            if follow.get('user'):

                uid = follow.get('user').get('id')

                yield Request(self.user_url.format(uid=uid), callback=self.parse_user)



        uid = response.meta.get('uid')

        # 關注列表

        user_relation_item = UserRelationItem()

        follows = [{'id': follow.get('user').get('id'), 'name': follow.get('user').get('screen_name')} for follow in follows]

        user_relation_item['id'] = uid

        user_relation_item['follows'] = follows

        user_relation_item['fans'] = []

        yield user_relation_item

        # 下一頁關注

        page = response.meta.get('page') + 1

        yield Request(self.follow_url.format(uid=uid, page=page),callback=self.parse_follows, meta={'page': page, 'uid': uid})

在這個方法裡面做了如下三件事:

  1. 解析關注列表中的每個使用者資訊併發起新的解析請求。我們首先解析關注列表的資訊,得到使用者的 ID ,然後再利用 user_url 構造訪問使用者詳情的 Request ,回撥就是剛才所定義的parse_user()方法 。
  1. 提取使用者關注列表內的關鍵資訊並生成 UserRelationitem 。 id 欄位直接設定成使用者的 ID,JSON 返回資料中的使用者資訊有很多冗餘欄位 。 在這裡我們只提取了關注使用者的 lD 和使用者名稱,然後把它們賦值給 follows 欄位, fans 欄位設定成空列表。 這樣我們就建立了一個存有使用者 ID 和使用者部分關注列表的 UserRelationitem ,之後合併且儲存具有同 一個 lD 的UserRelationit凹的關注和粉絲列表 。
  1. 提取下一頁關注。 只需要將此請求的分頁頁碼加 l 即可 。 分頁頁碼通過 Request 的 meta 屬性進行傳遞, Response 的meta 來接收。 這樣我們構造並返回下一頁的關注列表的 Request。

抓取粉絲列表的原理和抓取關注列表原理相同

來抓取使用者的微博資訊;

def parse_weibos(self, response):

    """

    解析微博列表

    :param response: Response物件

    """

    result = json.loads(response.text)

    if result.get('ok') and result.get('data').get('cards'):

        weibos = result.get('data').get('cards')

        for weibo in weibos:

            mblog = weibo.get('mblog')

            if mblog:

                weibo_item = WeiboItem()

                field_map = {

                    'id': 'id', 'attitudes_count': 'attitudes_count', 'comments_count': 'comments_count',

                    'reposts_count': 'reposts_count', 'picture': 'original_pic', 'pictures': 'pics',

                    'created_at': 'created_at', 'source': 'source', 'text': 'text', 'raw_text': 'raw_text',

                    'thumbnail': 'thumbnail_pic',

                }

                for field, attr in field_map.items():

                    weibo_item[field] = mblog.get(attr)

                weibo_item['user'] = response.meta.get('uid')

                yield weibo_item

        # 下一頁微博

        uid = response.meta.get('uid')

        page = response.meta.get('page') + 1

        yield Request(self.weibo_url.format(uid=uid, page=page), callback=self.parse_weibos,meta={'uid': uid, 'page': page})

資訊抓取結果:

使用者資訊

粉絲資訊:

關注資訊:

抓取列表外使用者資訊:

2.4 資料清洗

有些微博的時間可能不是標準的時間,比如它可能顯示為剛剛、幾分鐘前、幾小時前、昨天等。這裡我們需要統一轉化這些時間,實現一個 parse_time ()方法,如下所示:

def parse_time(self, date):

    if re.match('剛剛', date):

        date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time()))

    if re.match('\d+分鐘前', date):

        minute = re.match('(\d+)', date).group(1)

        date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time() - float(minute) * 60))

    if re.match('\d+小時前', date):

        hour = re.match('(\d+)', date).group(1)

        date = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time() - float(hour) * 60 * 60))

    if re.match('昨天.*', date):

        date = re.match('昨天(.*)', date).group(1).strip()

        date = time.strftime('%Y-%m-%d', time.localtime() - 24 * 60 * 60) + ' ' + date

    if re.match('\d{2}-\d{2}', date):

        date = time.strftime('%Y-', time.localtime()) + date + ' 00:00'

    return date

用正則來提取一些關鍵數字,用 time 庫來實現標準時間的轉換。

上面資料清洗的工作主要是對時間做清洗。

2.5資料儲存

資料清洗完畢之後,我們就要將資料儲存到 MongoDB 資料庫 。 我們在這裡實現 MongoPipeline類,如下所示:

class MongoPipeline(object):

    def __init__(self, mongo_uri, mongo_db):

        self.mongo_uri = mongo_uri

        self.mongo_db = mongo_db



    @classmethod

    def from_crawler(cls, crawler):

        return cls(

            mongo_uri=crawler.settings.get('MONGO_URI'),

            mongo_db=crawler.settings.get('MONGO_DATABASE')

        )



    def open_spider(self, spider):

        self.client = pymongo.MongoClient(self.mongo_uri)

        self.db = self.client[self.mongo_db]

        self.db[UserItem.collection].create_index([('id', pymongo.ASCENDING)])

        self.db[WeiboItem.collection].create_index([('id', pymongo.ASCENDING)])



    def close_spider(self, spider):

        self.client.close()



    def process_item(self, item, spider):

        if isinstance(item, UserItem) or isinstance(item, WeiboItem):

            self.db[item.collection].update({'id': item.get('id')}, {'$set': item}, True)

        if isinstance(item, UserRelationItem):

            self.db[item.collection].update(

                {'id': item.get('id')},

                {'$addToSet':

                    {

                        'follows': {'$each': item['follows']},

                        'fans': {'$each': item['fans']}

                    }

                }, True)

        return item

在這裡實現一個 Middleware,為每個 Request 新增隨機的 Cookies

2.7代理池對接

啟動redis提供資料儲存

執行代理池程式

獲取代理IP:

在微博抓取中的程式中啟動代理ip服務

555代表優先順序,設定完畢

文章轉載需要授權,版權所有,原始碼關注微信公眾號:大學自知

回覆:新浪爬蟲

獲取