今天你想爬谁的粉呢?谁粉多,就爬谁。那谁有粉?沉默王二有粉。
今天咱们继续学习 Python 爬虫,从本篇博客开始进行短暂的(15 篇)多线程爬虫学习。
第一篇就要采集 大佬@沉默王二 的粉丝,坐拥 27W+ 读者,属实让人羡慕。
目标数据源分析
本次要抓取的数据源是 blog.csdn.net/qing_gee?type=sub&subType=fans,其中的 ID 可以切换为你希望采集的 ID,当然包括你自己的 ID。
该页面下滑刷新会自动请求一个 API 接口,即 https://blog.csdn.net/community/home-api/v1/get-fans-list?page=3&size=20&noMore=false&blogUsername=qing_gee,其中参数如下:
- page:页码,根据目标人粉丝总数 / 20 计算获取即可;
- size:每页数据,默认值 20;
- noMore:无用;
- blogUsername:博客用户名
同时在测试接口过程中,接口会返回异常数据,实测增加一个延时控制,可以大幅度提高接口数据返回稳定性。
{'code': 400, 'message': 'fail', 'data': None}正常接口数据返回如下图所示:
使用技术点说明
本次采用 Python 多线程实现数据的采集,编码使用 threading 模块进行多线程控制,本系列专栏从最简单的多线程开始进行学习,例如本例,一次性发起 5(可自定义)个请求。
完整代码如下所示,代码说明请参考注释部分与尾部说明
import threading from threading import Lock, Thread import time import os import requests import random class MyThread(threading.Thread): def __init__(self, name): super(MyThread, self).__init__() self.name = name def run(self): global urls lock.acquire() one_url = urls.pop() print("正在爬取:", one_url) lock.release() print("任意线程等待随机时间") time.sleep(random.randint(1,3)) res = requests.get(one_url, headers=self.get_headers(), timeout=5) if res.json()["code"] != 400: data = res.json()["data"]["list"] for user in data: name = user['username'] nickname = self.remove_character(user['nickname']) userAvatar = user['userAvatar'] blogUrl = user['blogUrl'] blogExpert = user['blogExpert'] briefIntroduction = self.remove_character( user['briefIntroduction']) with open('./qing_gee_data.csv', 'a+', encoding='utf-8') as f: print(f'{name},{nickname},{userAvatar},{blogUrl},{blogExpert},{briefIntroduction}') f.write(f"{name},{nickname},{userAvatar},{blogUrl},{blogExpert},{briefIntroduction}\n") else: print(res.json()) print("异常数据", one_url) with open('./error.txt', 'a+', encoding='utf-8') as f: f.write(one_url+"\n") # 去除特殊字符 def remove_character(self, origin_str): if origin_str is None: return origin_str = origin_str.replace('\n', '') origin_str = origin_str.replace(',', ',') return origin_str # 获取随机UA请求头 def get_headers(self): uas = [ "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" ] ua = random.choice(uas) # 特别注意下述 cookie 部分,需要手动从开发者工具中进行复制,否则抓取到的数据,缺少nikename 与个人简介部分 headers = { "user-agent": ua, 'cookie': 'UserName=你的ID; UserInfo=你的UserInfo; UserToken=你的UserToken;', "referer": "https://blog.csdn.net/qing_gee?type=sub&subType=fans" } return headers if __name__ == '__main__': lock = Lock() url_format = 'https://blog.csdn.net/community/home-api/v1/get-fans-list?page={}&size=20&noMore=false&blogUsername=qing_gee' urls = [url_format.format(i) for i in range(1, 13300)] l = [] while len(urls) > 0: print(len(urls)) for i in range(5): p = MyThread("t"+str(i)) l.append(p) p.start() for p in l: p.join()代码运行结果如下图所示:
上述代码用到了多线程,也同时用到了线程锁,简单的多线程代码可以抽象为下述内容。
简单的多线程代码:
import threading import time def run(n): print('task', n) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=run, args=('t1',)) t2 = threading.Thread(target=run, args=('t2',)) t1.start() t2.start()其中比较核心的代码是 threading.Thread,参数 target 后面的值是函数名,args 是传递的参数,注意必须为元组类型。
爬虫代码还是用了共享全局变量,简化代码如下所示,其中重点学习 lock=Lock() 部分代码,以及在使用全局变量前后的 lock.acquire() 和 lock.release()。其中还用到了线程的 join 方法,该方法主要是为了让主线程等待子线程执行。
import threading from threading import Lock,Thread import time,os def work(): global urls lock.acquire() # 获取一个 url one_url = urls.pop() lock.release() print("得到的 URL 为",one_url) if __name__ == '__main__': lock = Lock() url_format = 'https://blog.csdn.net/community/home-api/v1/get-fans-list?page={}&size=20&noMore=false&blogUsername=qing_gee' # 拼接URL,全局共享变量 urls = [url_format.format(i) for i in range(1, 13300)] l = [] # 开启线程数量 for i in range(3): p = Thread(target=work) l.append(p) p.start() for p in l: p.join()拿到这些数据,可以针对性的去描述一个作者的用户画像了,本部分在后续的博客中为大家单独开一篇详细介绍。
代码在数据清理部分,还有优化的空间,由于设置了 13300 页数据,故最终抓取到 26W+数据,查询了一下,存在 梦想橡皮擦。
关注者中至少有 83 位博客专家,可以看到博客专家的个人简介写的都比较清楚,同时发现 jiangtao(CSDN 创始人)
收藏时间
==来都来了,不发个评论,点个赞,收个藏吗?==
今天是持续写作的第 <font color=red>202</font> / 365 天。可以<font color=#04a9f4>关注</font>我,<font color=#04a9f4>点赞</font>我、<font color=#04a9f4>评论</font>我、<font color=#04a9f4>收藏</font>我啦。