实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

要抓微博数据,第一步便是模拟登陆,因为很多信息(比如用户信息,用户主页微博数据翻页等各种翻页)都需要在登录状态下才能查看

这里我简单说一下,做爬虫的同学不要老想着用什么机器学习的方法去识别复杂验证码,真的难度非常大,这应该也不是一个爬虫工程师的工作重点,当然这只是我的个人建议。工程化的项目,我还是建议大家通过打码平台来解决验证码的问题。

实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

策略我们都清楚了。就该是分析和编码了。

我们先来分析如何构造用户信息的URL。这里我以微博名为一起神吐槽的博主为例进行分析。做爬虫的话,一个很重要的意识就是爬虫能抓的数据都是人能看到的数据,反过来,人能在浏览器上看到的数据,爬虫几乎都能抓。这里用的是几乎,因为有的数据抓取难度特别。我们首先需要以正常人的流程看看怎么获取用户的信息。我们先进入该博主的主页,如下图

实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

进群:548377875    即可获取大量的PDF以及教学视频哦!希望你能通过Python拿到高薪!

实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

种子博主具体信息

这里我们就看到了他的具体信息了。然后,我们看该页面的url构造

weibo.com/p/100505175…

我直接copy的地址栏的url。这样做有啥不好的呢?对于老鸟来说,一下就看出来了,这样做的话,可能会导致信息不全,因为可能有些信息是动态加载的。所以,我们需要通过抓包来判断到底微博会通过该url返回所有信息,还是需要请求一些ajax 链接才会返回一些关键信息。这里我就重复一下我的观点:抓包很重要,抓包很重要,抓包很重要!重要的事情说三遍。

我们抓完包,发现并没有ajax请求。那么可以肯定请求前面的url,会返回所有信息。我们通过点击鼠标右键,查看网页源代码,然后ctrl+a、ctrl+c将所有的页面源码保存到本地,这里我命名为personinfo.html。我们用浏览器打开该文件,发现我们需要的所有信息都在这段源码中,这个工作和抓包判断数据是否全面有些重复,但是在我看来是必不可少的,因为我们解析页面数据的时候还可以用到这个html文件,如果我们每次都通过网络请求去解析内容的话,那么可能账号没一会儿就会被封了(因为频繁访问微博信息),所以我们需要把要解析的文件保存到本地。

从上面分析中我们可以得知

weibo.com/p/100505175…

这个url就是获取用户数据的url。那么我们在只知道用户id的时候怎么构造它呢?我们可以多拿几个用户id来做测试,看构造是否有规律,比如我这里以用户名为网易云音乐的用户做分析,发现它的用户信息页面构造如下

weibo.com/1721030997/…

这个就和上面那个不同了。但是我们仔细观察,可以发现上面那个是个人用户,下面是企业微博用户。我们尝试一下把它们url格式都统一为第一种或者第二种的格式

weibo.com/1751195602/…

这样会出现404,那么统一成上面那种呢?

weibo.com/p/100505172…

这样子的话,它会被重定向用户主页,而不是用户详细资料页。所以也就不对了。那么该以什么依据判断何时用第一种url格式,何时用第二种url格式呢?我们多翻几个用户,会发现除了100505之外,还有100305、100206等前缀,那么我猜想这个应该可以区分不同用户。这个前缀在哪里可以得到呢?我们打开我们刚保存的页面源码,搜索100505,可以发现

实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

domain

微博应该是根据这个来区分不同用户类型的。这里大家可以自己也可以试试,看不同用户的domain是否不同。为了数据能全面,我也是做了大量测试,发现个人用户的domain是1005051,作家是100305,其他基本都是认证的企业号。前两个个人信息的url构造就是

weibo.com/p/domain+ui…

后者的是

weibo.com/uid/about

弄清楚了个人信息url的构造方式,但是还有一个问题。我们已知只有uid啊,没有domain啊。如果是企业号,我们通过domain=100505会被重定向到主页,如果是作家等(domain=100305或者100306),也会被重定向主页。我们在主页把domain提取出来,再请求一次,不就能拿到用户详细信息了吗?

关于如何构造获取用户信息的url的相关分析就到这里了。因为我们是在登录的情况下进行数据抓取的,可能在抓取的时候,某个账号突然就被封了,或者由于网络原因,某次请求失败了,该如何处理?对于前者,我们需要判断每次请求返回的内容是否符合预期,也就是看response url是否正常,看response content是否是404或者让你验证手机号等,对于后者,我们可以做一个简单的重试策略,大概代码如下

@timeout_decorator

def get_page(url,user_verify=True,need_login=True):

"""

:param url: 待抓取url

:param user_verify: 是否为可能出现验证码的页面(ajax连接不会出现验证码,如果是请求微博或者用户信息可能出现验证码),否为抓取转发的ajax连接

:param need_login: 抓取页面是否需要登录,这样做可以减小一些账号的压力

:return: 返回请求的数据,如果出现404或者403,或者是别的异常,都返回空字符串

"""

crawler.info('本次抓取的url为{url}'.format(url=url))

count = 0

while count < max_retries:
 if need_login:
 # 每次重试的时候都换cookies,并且和上次不同,如果只有一个账号,那么就允许相同
 name_cookies = Cookies.fetch_cookies()
 if name_cookies is None:
 crawler.warning('cookie池中不存在cookie,正在检查是否有可用账号')
 rs = get_login_info()
 # 选择状态正常的账号进行登录,账号都不可用就停掉celery worker
 if len(rs) == 0:
 crawler.error('账号均不可用,请检查账号健康状况')
 # 杀死所有关于celery的进程
 if 'win32' in sys.platform:
 os.popen('taskkill /F /IM "celery*"')
 else:
 os.popen('pkill -f "celery"')
 else:
 crawler.info('重新获取cookie中...')
 login.excute_login_task()
 time.sleep(10)
 try:
 if need_login:
 resp = requests.get(url,headers=headers,cookies=name_cookies[1],timeout=time_out,verify=False)
 if "$CONfig['islogin'] = '0'" in resp.text:
 crawler.warning('账号{}出现异常'.format(name_cookies[0]))
 freeze_account(name_cookies[0],0)
 Cookies.delete_cookies(name_cookies[0])
 continue
 else:
 resp = requests.get(url,verify=False)
 page = resp.text
 if page:
 page = page.encode('utf-8','ignore').decode('utf-8')
 else:
 continue
 # 每次抓取过后程序sleep的时间,降低封号危险
 time.sleep(interal)
 if user_verify:
 if 'unfreeze' in resp.url or 'accessdeny' in resp.url or 'userblock' in resp.url or is_403(page):
 crawler.warning('账号{}已经被冻结'.format(name_cookies[0]))
 freeze_account(name_cookies[0],0)
 Cookies.delete_cookies(name_cookies[0])
 count += 1
 continue
 if 'verifybmobile' in resp.url:
 crawler.warning('账号{}功能被锁定,需要手机解锁'.format(name_cookies[0]))
 freeze_account(name_cookies[0],-1)
 Cookies.delete_cookies(name_cookies[0])
 continue
 if not is_complete(page):
 count += 1
 continue
 if is_404(page):
 crawler.warning('url为{url}的连接不存在'.format(url=url))
 return ''
 except (requests.exceptions.ReadTimeout,requests.exceptions.ConnectionError,AttributeError) as e:
 crawler.warning('抓取{}出现异常,具体信息是{}'.format(url,e))
 count += 1
 time.sleep(excp_interal)
 else:
 Urls.store_crawl_url(url,1)
 return page
crawler.warning('抓取{}已达到最大重试次数,请在redis的失败队列中查看该url并检查原因'.format(url))
Urls.store_crawl_url(url,0)
return ''

这里大家把上述代码当一段伪代码读就行了,主要看看如何处理抓取时候的异常。因为如果贴整个用户抓取的代码,不是很现实,代码量有点大。

下面讲页面解析的分析。有一些做PC端微博信息抓取的同学,可能曾经遇到过这么个问题:保存到本地的html文件打开都能看到所有信息啊,为啥在页面源码中找不到呢?因为PC端微博页面的关键信息都是像下图这样,被FM.view()包裹起来的,里面的数据可能被json encode过。

实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

标签

那么这么多的FM.view(),我们怎么知道该提取哪个呢?这里有一个小技巧,由于只有中文会被编码,英文还是原来的样子,所以我们可以看哪段script中包含了渲染后的页面中的字符,那么那段应该就可能包含所有页面信息。我们这里以顶部的头像为例,如图

实习了一个多月!师傅终于教我案例了!分布式爬虫!这是我的笔记

我们在页面源码中搜索,只发现一个script中有该字符串,那么就是那段script是页面相关信息。我们可以通过正则表达式把该script提取出来,然后把其中的html也提取出来,再保存到本地,看看信息是否全面。这里我就不截图了。感觉还有很多要写的,不然篇幅太长了。

另外,对于具体页面的解析,我也不做太多的介绍了。太细的东西还是建议读读源码。我只讲一下,我觉得的一种处理异常的比较优雅的方式。微博爬虫的话,主要是页面样式太多,如果你打算包含所有不同的用户的模版,那么我觉得几乎不可能,不同用户模版,用到的解析规则就不一样。那么出现解析异常如何处理?尤其是你没有catch到的异常。很可能因为这个问题,程序就崩掉。其实对于Python这门语言来说,我们可以通过 装饰器 来捕捉我们没有考虑到的异常,比如我这个装饰器

def parse_decorator(return_type):

"""

:param return_type: 用于捕捉页面解析的异常,0表示返回数字0,1表示返回空字符串,2表示返回[],3表示返回False,4表示返回{},5返回None

:return: 0,'',[],False,{},None

"""

def page_parse(func): br/>@wraps(func)

def handle_error( keys):

try:

keys)

except Exception as e:

parser.error(e)

if return_type == 5:
 return None
 elif return_type == 4:
 return {}
 elif return_type == 3:
 return False
 elif return_type == 2:
 return []
 elif return_type == 1:
 return ''
 else:
 return 0
 return handle_error
return page_parse

上面的代码就是处理解析页面发生异常的情况,我们只能在数据的准确性、全面性和程序的健壮性之间做一些取舍。用装饰器的话,程序中不用写太多的 try语句,代码重复率也会减少很多。

页面的解析由于篇幅所限,我就讲到这里了。没有涉及太具体的解析,其中一个还有一个比较难的点,就是数据的全面性,读者可以去多观察几个微博用户的个人信息,就会发现有的个人信息,有的用户有填写,有的并没有。解析的时候要考虑完的话,建议从自己的微博的个人信息入手,看到底有哪些可以填。这样可以保证几乎不会漏掉一些重要的信息。

最后,我再切合本文的标题,讲如何搭建一个分布式的微博爬虫。开发过程中,我们可以先就做单机单线程的爬虫,然后再改成使用celery的方式。这里这样做是为了方便开发和测试,因为你单机搭起来并且跑得通了,那么分布式的话,就很容易改了,因为celery的API使用本来就很简洁。

我们抓取的是用户信息和他的关注和粉丝uid。用户信息的话,我们一个请求大概能抓取一个用户的信息,而粉丝和关注我们一个请求可以抓取18个左右(因为这个抓的是列表),显然可以发现用户信息应该多占一些请求的资源。这时候就该介绍理论篇没有介绍的关于celery的一个高级特性了,它叫做任务路由。直白点说,它可以规定哪个分布式节点能做哪些任务,不能做哪些任务。它的存在可以让资源分配更加合理, 分布式微博爬虫项目初期,就没有使用任务路由,然后抓了十多万条关注和分析,结果发现用户信息抓几万条,这就是资源分配得不合理。那么如何进行任务路由呢?

coding:utf-8

import os

from datetime import timedelta

from celery import Celery

from kombu import Exchange,Queue

from config.conf import get_broker_or_backend

from celery import platforms

允许celery以root身份启动

platforms.C_FORCE_ROOT = True

worker_log_path = os.path.join(os.path.dirname(os.path.dirname( file ))+'/logs','celery.log')

beat_log_path = os.path.join(os.path.dirname(os.path.dirname( file ))+'/logs','beat.log')

tasks = ['tasks.login','tasks.user']

include的作用就是注册服务化函数

app = Celery('weibo_task',include=tasks,broker=get_broker_or_backend(1),backend=get_broker_or_backend(2))

app.conf.update(

CELERY_TIMEZONE='Asia/Shanghai',

CELERY_ENABLE_UTC=True,

CELERYD_LOG_FILE=worker_log_path,

CELERYBEAT_LOG_FILE=beat_log_path,

CELERY_ACCEPT_CONTENT=['json'],

CELERY_TASK_SERIALIZER='json',

CELERY_RESULT_SERIALIZER='json',

CELERY_QUEUES=(

Queue('login_queue',exchange=Exchange('login',type='direct'),routing_key='for_login'),

Queue('user_crawler',exchange=Exchange('user_info',routing_key='for_user_info'),

Queue('fans_followers',exchange=Exchange('fans_followers',routing_key='for_fans_followers'),

)

上述代码我指定了有login_queue、user_crawler、fans_followers三个任务队列。它们分别的作用是登录用户信息抓取、粉丝和关注抓取。现在假设我有三台爬虫服务器A、B和C。我想让我所有的账号登录任务分散到三台服务器、让用户抓取在A和B上执行,让粉丝和关注抓取在C上执行,那么启动A、B、C三个服务器的celery worker的命令就分别是

celery -A tasks.workers -Q login_queue,user_crawler worker -l info -c 1 # A服务器和B服务器启动worker的命令,它们只会执行登录用户信息抓取任务

celery -A tasks.workers -Q login_queue,fans_followers worker -l info -c 1 # C服务器启动worker的命令,它只会执行登录、粉丝和关注抓取任务

然后我们通过命令行或者代码(如下)就能发送所有任务给各个节点执行了

coding:utf-8

from tasks.workers import app

from page_get import user as user_get

from db.seed_ids import get_seed_ids,get_seed_by_id,insert_seeds,set_seed_other_crawled

@app.task(ignore_result=True)

def crawl_follower_fans(uid):

seed = get_seed_by_id(uid)

if seed.other_crawled == 0:

rs = user_get.get_fans_or_followers_ids(uid,1)

rs.extend(user_get.get_fans_or_followers_ids(uid,2))

datas = set(rs)

重复数据跳过插入

if datas:
 insert_seeds(datas)
 set_seed_other_crawled(uid)

@app.task(ignore_result=True)

def crawl_person_infos(uid):

"""

根据用户id来爬取用户相关资料和用户的关注数和粉丝数(由于微博服务端限制,认爬取前五页,企业号的关注和粉丝也不能查看)

:param uid: 用户id

:return:

"""

if not uid:

return

# 由于与别的任务共享数据表,所以需要先判断数据库是否有该用户信息,再进行抓取
user = user_get.get_profile(uid)
# 不抓取企业号
if user.verify_type == 2:
 set_seed_other_crawled(uid)
 return
app.send_task('tasks.user.crawl_follower_fans',args=(uid,),queue='fans_followers',routing_key='for_fans_followers')

@app.task(ignore_result=True)

def excute_user_task():

seeds = get_seed_ids()

if seeds:

for seed in seeds:

在send_task的时候指定任务队列

app.send_task('tasks.user.crawl_person_infos',args=(seed.uid,queue='user_crawler',routing_key='for_user_info')

这里我们是通过 queue='user_crawler',routing_key='for_user_info'来将任务和worker进行关联的。

关于celery任务路由的更详细的资料请阅读官方文档。

到这里,基本把微博信息抓取的过程和分布式进行抓取的过程都讲完了,具体实现分布式的方法,可以读读基础篇。由于代码量比较大,我并没有贴上完整的代码,只讲了要点。分析过程是讲的抓取过程的分析和页面解析的分析,并在最后,结合分布式,讲了一下使用任务队列来让分布式爬虫更加灵活和可扩展。

如果有同学想跟着做一遍,可能需要参考分布式微博爬虫的源码,自己动手实现一下,或者跑一下,印象可能会更加深刻。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


我最近重新拾起了计算机视觉,借助Python的opencv还有face_recognition库写了个简单的图像识别demo,额外定制了一些内容,原本想打包成exe然后发给朋友,不过在这当中遇到了许多小问题,都解决了,记录一下踩过的坑。 1、Pyinstaller打包过程当中出现warning,跟d
说到Pooling,相信学习过CNN的朋友们都不会感到陌生。Pooling在中文当中的意思是“池化”,在神经网络当中非常常见,通常用的比较多的一种是Max Pooling,具体操作如下图: 结合图像理解,相信你也会大概明白其中的本意。不过Pooling并不是只可以选取2x2的窗口大小,即便是3x3,
记得大一学Python的时候,有一个题目是判断一个数是否是复数。当时觉得比较复杂不好写,就琢磨了一个偷懒的好办法,用异常处理的手段便可以大大程度帮助你简短代码(偷懒)。以下是判断整数和复数的两段小代码: 相信看到这里,你也有所顿悟,能拓展出更多有意思的方法~
文章目录 3 直方图Histogramplot1. 基本直方图的绘制 Basic histogram2. 数据分布与密度信息显示 Control rug and density on seaborn histogram3. 带箱形图的直方图 Histogram with a boxplot on t
文章目录 5 小提琴图Violinplot1. 基础小提琴图绘制 Basic violinplot2. 小提琴图样式自定义 Custom seaborn violinplot3. 小提琴图颜色自定义 Control color of seaborn violinplot4. 分组小提琴图 Group
文章目录 4 核密度图Densityplot1. 基础核密度图绘制 Basic density plot2. 核密度图的区间控制 Control bandwidth of density plot3. 多个变量的核密度图绘制 Density plot of several variables4. 边
首先 import tensorflow as tf tf.argmax(tenso,n)函数会返回tensor中参数指定的维度中的最大值的索引或者向量。当tensor为矩阵返回向量,tensor为向量返回索引号。其中n表示具体参数的维度。 以实际例子为说明: import tensorflow a
seaborn学习笔记章节 seaborn是一个基于matplotlib的Python数据可视化库。seaborn是matplotlib的高级封装,可以绘制有吸引力且信息丰富的统计图形。相对于matplotlib,seaborn语法更简洁,两者关系类似于numpy和pandas之间的关系,seabo
Python ConfigParser教程显示了如何使用ConfigParser在Python中使用配置文件。 文章目录 1 介绍1.1 Python ConfigParser读取文件1.2 Python ConfigParser中的节1.3 Python ConfigParser从字符串中读取数据
1. 处理Excel 电子表格笔记(第12章)(代码下载) 本文主要介绍openpyxl 的2.5.12版处理excel电子表格,原书是2.1.4 版,OpenPyXL 团队会经常发布新版本。不过不用担心,新版本应该在相当长的时间内向后兼容。如果你有新版本,想看看它提供了什么新功能,可以查看Open
1. 发送电子邮件和短信笔记(第16章)(代码下载) 1.1 发送电子邮件 简单邮件传输协议(SMTP)是用于发送电子邮件的协议。SMTP 规定电子邮件应该如何格式化、加密、在邮件服务器之间传递,以及在你点击发送后,计算机要处理的所有其他细节。。但是,你并不需要知道这些技术细节,因为Python 的
文章目录 12 绘图实例(4) Drawing example(4)1. Scatterplot with varying point sizes and hues(relplot)2. Scatterplot with categorical variables(swarmplot)3. Scat
文章目录 10 绘图实例(2) Drawing example(2)1. Grouped violinplots with split violins(violinplot)2. Annotated heatmaps(heatmap)3. Hexbin plot with marginal dist
文章目录 9 绘图实例(1) Drawing example(1)1. Anscombe’s quartet(lmplot)2. Color palette choices(barplot)3. Different cubehelix palettes(kdeplot)4. Distribution
Python装饰器教程展示了如何在Python中使用装饰器基本功能。 文章目录 1 使用教程1.1 Python装饰器简单示例1.2 带@符号的Python装饰器1.3 用参数修饰函数1.4 Python装饰器修改数据1.5 Python多层装饰器1.6 Python装饰器计时示例 2 参考 1 使
1. 用GUI 自动化控制键盘和鼠标第18章 (代码下载) pyautogui模块可以向Windows、OS X 和Linux 发送虚拟按键和鼠标点击。根据使用的操作系统,在安装pyautogui之前,可能需要安装一些其他模块。 Windows: 不需要安装其他模块。OS X: sudo pip3
文章目录 生成文件目录结构多图合并找出文件夹中相似图像 生成文件目录结构 生成文件夹或文件的目录结构,并保存结果。可选是否滤除目录,特定文件以及可以设定最大查找文件结构深度。效果如下: root:[z:/] |--a.py |--image | |--cat1.jpg | |--cat2.jpg |
文章目录 VENN DIAGRAM(维恩图)1. 具有2个分组的基本的维恩图 Venn diagram with 2 groups2. 具有3个组的基本维恩图 Venn diagram with 3 groups3. 自定义维恩图 Custom Venn diagram4. 精致的维恩图 Elabo
mxnet60分钟入门Gluon教程代码下载,适合做过深度学习的人使用。入门教程地址: https://beta.mxnet.io/guide/getting-started/crash-course/index.html mxnet安装方法:pip install mxnet 1 在mxnet中使
文章目录 1 安装2 快速入门2.1 基本用法2.2 输出图像格式2.3 图像style设置2.4 属性2.5 子图和聚类 3 实例4 如何进一步使用python graphviz Graphviz是一款能够自动排版的流程图绘图软件。python graphviz则是graphviz的python实