草庐IT

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

程序员_李白 2023-03-28 原文

前言

仅限交流学习:

需求:抓取某用户里的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        :lingjie2014
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秒后再继续。

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

有关v博Python爬虫实战案例,多线程实现3小时100万量的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  4. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  5. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  6. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  7. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐