v博Python爬虫实战案例,多线程实现3小时100万量

前言

仅限交流学习:

需求:抓取某用户里的fans信息
注意:为了平台审核,以下代码里的网址用xxxxx.com代替,请自行替换。


一、抓包分析

通过抓包分析,可以看到有个friends的数据包就有我们想要的信息:


image.png

每个包只有20条数据,再来翻页抓下一个包:


image.png

image.png

对比两个包,很明显page数就是下一页,uid是用户id,用户id也就是用户主页链接的后面一段,type直接固定fans就行

二、测试请求

上Pycharm,构建传参和headers,这里注意要登录后的cookie:

headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36',
        'cookie': cookie
    }
def getFriendsList(uid, uname):
    url = 'https://xxxxx.com/ajax/friendships/friends'
    f = open(out_path + '/' + uname + '.txt', 'a+', encoding='utf-8')
    for page in range(1, 101):
        print(f'{uid} {uname} 第 {page} 页')
        time.sleep(0.5)
        try:
            params = {
                'relate': 'fans',
                'page': page,
                'uid': uid,
                'type': 'fans',
                'newFollowerCount': 0,
            }
            res = requests.get(url, headers=headers, params=params)
            if res.status_code == 414:
                return '414'
            json_data = res.json()
            users = json_data.get('users')
            if users:
                for user in users:
                    user_id = user.get('idstr')  # 用户id
                    user_name = user.get('name')  # 用户名
                    followers_count = user.get('followers_count_str')  # 关注数
                    friends_count = user.get('friends_count')  # fans数
                    description = user.get('description')  # 简介
                    location = user.get('location')  # ip属地
                    statuses_count = user.get('statuses_count')  # 全部发文数
                    profile_url = 'https://xxxxx.com/' + user.get('profile_url')  # 主页链接
                    # print(user_name, followers_count, friends_count, description, location, statuses_count, profile_url)
                    f.write(f'{user_name},{followers_count},{friends_count},{description},{location},{statuses_count},{profile_url}\n')
        except Exception as e:
            print(e)
            return None
    f.close()
    return 'ok'

image.png

测试数据没问题,但是每个用户粉丝页最多只能查询100页的数据,每页20条,也就是2000条,那怎么能采集到100万条数据呢?很简单,我们来准备500个这样的用户主页,每个用户采集2000,这样就是100万了:


image.png

三、读取表格

有了准备文件,得先读取出来到一个列表吧:

def getExcelData(input_file):
    # 读取表格数据
    wb = openpyxl.load_workbook(input_file)
    sh_names = wb.sheetnames
    s1 = wb[sh_names[0]]
    try:
        data_list = []
        for index, item in enumerate(list(s1.rows)[1:]):
            # 读取每行信息
            values = []
            for val in item:
                values.append(val.value)
            data_list.append(values)  # 添加行数据到列表
        return data_list
    except:
        return None

四、点击关注

经过测试,有一些用户的粉丝列表并不可见,需要点击关注后才能看见,所以我们得来解决这个问题:

以这个为例,未关注前,是看不到粉丝列表的:


image.png

然后我们来点击一下关注,可以看到有个create的包发起了一个post请求,所以我们来写个判断,如果粉丝列表返回内容为空,那么我们来执行一下这个点击关注的函数,然后再次执行粉丝列表获取函数:

def createPost(uid):
    url = 'https://xxxxx.com/ajax/friendships/create'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36',
        'cookie': cookie,
        'x-xsrf-token': 'mBqiNo6lC0vAzPfp-uUX4Z3q',
        'XMLHttpRequest': 'XMLHttpRequest',
        'origin': 'https://xxxxx.com',
        'content-type': 'application/json;charset=UTF-8',
    }
    data = {
        "friend_uid": str(uid),
        "lpage": "profileRecom",
        "page": "profile"
    }
    try:
        res = requests.post(url, headers=headers, data=json.dumps(data)).json()
        ok = res.get('ok')
        if ok == 1:
            print('关注成功')
            return
    except Exception as e:
        return

五、多线程爬取

到这里,大部分代码已经写完了,数据量比较大,我们不可能用单线程跑吧,所以我们需要写一个多线程:

   
def matching(i, data_list, st, ed, ans_file):
    for item in data_list[st:ed]:
        time.sleep(1)
        user_name = item[0]
        uid = str(item[2]).split('/')[-1]
        result = getFriendsList(uid, user_name)
        if result == '414':
            time.sleep(120)
            print('访问频繁,休息一会...')
        if result is None:
            time.sleep(1)
            createPost(uid)
            time.sleep(1)
            getFriendsList(uid, user_name)
st = 0
diff = ceil(len(data_list) / THREAD_NUM)
thread_pool = []
for i in range(THREAD_NUM):
    ans_file = os.path.join(out_path, str(i) + ".txt")
    thread = Thread(target=matching, args=(i, data_list, diff * i + st, min(diff * (i + 1) + st, len(data_list)), ans_file))
    thread_pool.append(thread)
for i in range(THREAD_NUM):
    thread_pool[i].start()
for i in range(THREAD_NUM):
    thread_pool[i].join()

最后,再写个函数让多线程的文件合并成一个文件,直接上全部代码吧:

# !/usr/bin/env python
# -*- coding:utf-8 -*-
# Time      :2022/6/17 14:31
# Author    :JACK
# VX        :JackLi_1900
import json
import openpyxl
import requests
import csv
import os
from math import ceil
from threading import Thread
import time
THREAD_NUM = 8

def mkdir(path):
    path = path.strip()
    isExists = os.path.exists(path)
    if not isExists:
        os.makedirs(path)

def getExcelData(input_file):
    # 读取表格数据
    wb = openpyxl.load_workbook(input_file)
    sh_names = wb.sheetnames
    s1 = wb[sh_names[0]]
    try:
        data_list = []
        for index, item in enumerate(list(s1.rows)[1:]):
            # 读取每行信息
            values = []
            for val in item:
                values.append(val.value)
            data_list.append(values)  # 添加行数据到列表
        return data_list
    except:
        return None

def createPost(uid):
    url = 'https://xxxxx.com/ajax/friendships/create'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36',
        'cookie': cookie,
        'x-xsrf-token': 'mBqiNo6lC0vAzPfp-uUX4Z3q',
        'XMLHttpRequest': 'XMLHttpRequest',
        'origin': 'https://xxxxx.com',
        'content-type': 'application/json;charset=UTF-8',
    }
    data = {
        "friend_uid": str(uid),
        "lpage": "profileRecom",
        "page": "profile"
    }
    try:
        res = requests.post(url, headers=headers, data=json.dumps(data)).json()
        ok = res.get('ok')
        if ok == 1:
            print('关注成功')
            return
    except Exception as e:
        return

def getFriendsList(uid, uname):
    url = 'https://xxxxx.com/ajax/friendships/friends'
    f = open(out_path + '/' + uname + '.txt', 'a+', encoding='utf-8')
    for page in range(1, 101):
        print(f'{uid} {uname} 第 {page} 页')
        time.sleep(0.5)
        try:
            params = {
                'relate': 'fans',
                'page': page,
                'uid': uid,
                'type': 'fans',
                'newFollowerCount': 0,
            }
            res = requests.get(url, headers=headers, params=params)
            if res.status_code == 414:
                return '414'
            json_data = res.json()
            users = json_data.get('users')
            if users:
                for user in users:
                    user_id = user.get('idstr')  # 用户id
                    user_name = user.get('name')  # 用户名
                    followers_count = user.get('followers_count_str')  # 关注数
                    friends_count = user.get('friends_count')  # fans数
                    description = user.get('description')  # 简介
                    location = user.get('location')  # ip属地
                    statuses_count = user.get('statuses_count')  # 全部发文数
                    profile_url = 'https://xxxxx.com/' + user.get('profile_url')  # 主页链接
                    print(user_name, followers_count, friends_count, description, location, statuses_count, profile_url)
                    f.write(f'{user_name},{followers_count},{friends_count},{description},{location},{statuses_count},{profile_url}\n')
        except Exception as e:
            print(e)
            return None
    f.close()
    return 'ok'

def matching(i, data_list, st, ed, ans_file):
    for item in data_list[st:ed]:
        time.sleep(1)
        user_name = item[0]
        uid = str(item[2]).split('/')[-1]
        result = getFriendsList(uid, user_name)
        if result == '414':
            time.sleep(120)
            print('访问频繁,休息一会...')
        if result is None:
            time.sleep(1)
            createPost(uid)
            time.sleep(1)
            getFriendsList(uid, user_name)

def merge_result_files(out_path):
    """
    合并多进程的输出文件
    :return:
    """
    print("合并输出文件")
    file_list = os.listdir(out_path)
    ans = open('result.txt', 'a+', encoding='utf-8')
    cnt = 0
    with open('all_data.csv', 'w', newline='', encoding='utf-8-sig') as fp:
        w = csv.writer(fp)
        w.writerow(['用户名', 'fans数量', '关注数量', '简介', 'IP属地', '全部V博数量', '主页URL'])
        for i in file_list:
            temp = os.path.join(out_path, i)
            with open(temp, 'r', encoding='utf-8') as f:
                for line in f.readlines():
                    cnt += 1
                    ans.write(line)
                    w.writerow(line.strip().split(','))
    ans.close()
    print("合并多进程输出文件成功")
    if cnt == 0:
        return 0
    return cnt

if __name__ == "__main__":
    cookie = ''
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36',
        'cookie': cookie
    }
    data_list = getExcelData('input_data.xlsx')
    out_path = 'out_data'
    mkdir(out_path)
    st = 0
    diff = ceil(len(data_list) / THREAD_NUM)
    thread_pool = []
    for i in range(THREAD_NUM):
        ans_file = os.path.join(out_path, str(i) + ".txt")
        thread = Thread(target=matching, args=(i, data_list, diff * i + st, min(diff * (i + 1) + st, len(data_list)), ans_file))
        thread_pool.append(thread)
    for i in range(THREAD_NUM):
        thread_pool[i].start()
    for i in range(THREAD_NUM):
        thread_pool[i].join()
    cnt = merge_result_files(out_path)    # 合并保存文件
    print(f'总数:{cnt}')



image.png

总结

我用了8线程跑了3小时左右,最终爬了98万多的数据量,因为有少部分爬取失败的,平台有速度限制,速度不宜太快,否则需要等2分钟左右才能继续访问粉丝列表页,可以看到我在代码里也写了,如果返回响应代码为414,则等待120秒后再继续。

好了,以上是全部内容,欢迎交流!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容