scrapy微博反爬虫_Scrapy 爬取新浪微博(解析api)
python爬虫学习笔记...
本文采用m.weibo.cn站点完成抓取,通过分析api提取数据,数据存储在MongoDB中。
爬虫文件
# -*- coding: utf-8 -*-
import scrapy
from scrapy import Request
from ..items import *
import json
from pyquery import PyQuery as pq
class WeiboSpiderSpider(scrapy.Spider):
name = 'weibo_spider'
allowed_domains = ['m.weibo.cn']
# start_urls = ['http://m.weibo.cn/']
# 用户
user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&value={uid}&containerid=100505{uid}'
# 微博
weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&page={page}&containerid=107603{uid}'
# 关注
follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}'
# 粉丝 注意 粉丝页码参数是since_id=,而不是关注页码中page=
fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&since_id={page}'
start_uids = [
'2803301701', # 人民日报
'1699432410', # 新华社
'1974576991', # 环球时报
'5476386628', # 侠客岛
]
def start_requests(self):
for uid in self.start_uids:
yield Request(self.user_url.format(uid=uid), callback=self.parse_user)
我们首先修改Spider,配置各个Ajax 的URL ,选取几个大V ,将他们的ID赋值成一个列表,重写start_requests ( )方法,也就是依次抓取各个大V的个人详情页,然后用parse_user( )进行解析
items.py
from scrapy import Item, Field
class UserItem(Item):
collection = 'users'
id = Field() # 用户id
name = Field() # 昵称
profile_image = Field() # 头像图片
cover_image = Field() # 背景图片
verified_reason = Field() # 认证
description = Field() # 简介
fans_count = Field() # 粉丝数
follows_count = Field() # 关注数
weibos_count = Field() # 微博数
mbrank = Field() # 会员等级
verified = Field() # 是否认证
verified_type = Field() # 认证类型
verified_type_ext = Field() # 以下不知道
gender = Field()
mbtype = Field()
urank = Field()
crawled_at = Field() # 抓取时间戳 在pipelines.py中
class UserRelationItem(Item):
collection = 'UserRelation'
id = Field()
follows = Field()
fans = Field()
class WeiboItem(Item):
collection = 'weibos'
id = Field()
idstr = Field()
edit_count = Field()
created_at = Field()
version = Field()
thumbnail_pic = Field()
bmiddle_pic = Field()
original_pic = Field()
source = Field()
user = Field()
text = Field()
crawled_at = Field()
这里定义了collection 字段,指明保存的Collection的名称。用户的关注和粉丝列表直接定义为一个单独的UserRelationitem ,其中id 就是用户的ID, follows 就是用户关注列表, fans 是粉丝列表。
提取数据
接着提取数据,解析用户信息,实现parse_user( )方法
# 解析用户信息
def parse_user(self, 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()
user_item['id'] = user_info.get('id') # 用户id
user_item['name'] = user_info.get('screen_name') # 昵称
user_item['profile_image'] = user_info.get('profile_image_url') # 头像图片
user_item['cover_image'] = user_info.get('cover_image_phone') # 背景图片
user_item['verified_reason'] = user_info.get('verified_reason') # 微博认证
user_item['description'] = user_info.get('description') # 简介
user_item['weibos_count'] = user_info.get('statuses_count') # 微博数
user_item['fans_count'] = user_info.get('followers_count') # 粉丝
user_item['follows_count'] = user_info.get('follow_count') # 关注数
user_item['mbrank'] = user_info.get('mbrank') # 会员等级
user_item['verified'] = user_info.get('verified') # 是否认证
user_item['verified_type'] = user_info.get('verified_type') # 认证类型
user_item['verified_type_ext'] = user_info.get('verified_type_ext') # 以下不知道是啥
user_item['gender'] = user_info.get('gender')
user_item['mbtype'] = user_info.get('mbtype')
user_item['urank'] = user_info.get('urank')
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})
在这里一共完成了两个操作。
解析JSON 提取用户信息并生成UserItem返回;
构造用户的关注、粉丝、微博的第一页的链接,并生成Request ,这里需要的参数只有用户的ID 。另外,初始分页页码直接设置为‘1’即可。
接下来实现解析所发所有微博的方法 parse_weibos( )
# 解析微博列表
def parse_weibos(self, response):
result = json.loads(response.text)
if result.get('ok') == 1 and result.get('data').get('cards'):
weibos = result.get('data').get('cards')
for weibo in weibos:
mblog = weibo.get('mblog')
# 判断是否存在mblog,有时不存在
if mblog:
weibo_item = WeiboItem()
weibo_item['id'] = mblog.get('id') # 微博id
weibo_item['idstr'] = mblog.get('idstr')
weibo_item['edit_count'] = mblog.get('edit_count')
weibo_item['created_at'] = mblog.get('created_at')
weibo_item['version'] = mblog.get('version')
weibo_item['thumbnail_pic'] = mblog.get('thumbnail_pic')
weibo_item['bmiddle_pic'] = mblog.get('bmiddle_pic')
weibo_item['original_pic'] = mblog.get('original_pic')
weibo_item['source'] = mblog.get('source')
weibo_item['user'] = response.meta.get('uid') # 用户id
# 检测有没有阅读全文:
all_text = mblog.get('text')
if '>全文
# 微博全文页面链接
all_text_url = 'https://m.weibo.cn/statuses/extend?id=' + mblog.get('id')
yield Request(all_text_url, callback=self.parse_all_text, meta={'item': weibo_item})
else:
text = pq(mblog.get('text')).text().replace('\n', '')
text = ''.join([x.strip() for x in text])
weibo_item['text'] = text
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})
# 有阅读全文的情况,获取全文
def parse_all_text(self, response):
result = json.loads(response.text)
if result.get('ok') and result.get('data'):
weibo_item = response.meta['item']
all_text = result.get('data').get('longTextContent')
text = pq(all_text).text().replace('\n', '')
text = ''.join([x.strip() for x in text])
weibo_item['text'] = text
yield weibo_item
在这里一共完成了两个操作。
一、解析JSON 提取微博信息并生成WeiboItem返回
解析微博内容text的时候分二种情况:
1.所发微博内容较长,微博内容中包含微博全文链接,如果有,进入到parse_all_text( )方法中获取全文;
2.不含全文链接,那直接获取微博内容。
二、构造用户微博的下一页链接
接下来解析解析用户关注列表和粉丝列表,原理相同
# 解析用户关注列表
def parse_follows(self, 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})
# 解析用户粉丝列表
def parse_fans(self, 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'):
# 解析用户
fans = result.get('data').get('cards')[-1].get('card_group')
# for fan in fans:
# if fan.get('user'):
# uid = fan.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()
fans = [{'id': fan.get('user').get('id'), 'name': fan.get('user').get('screen_name')} for fan in
fans]
user_relation_item['id'] = uid
user_relation_item['fans'] = fans
user_relation_item['follows'] = []
yield user_relation_item
# 下一页粉丝
page = response.meta.get('page') + 1
yield Request(self.fan_url.format(uid=uid, page=page),
callback=self.parse_fans, meta={'page': page, 'uid': uid})
按照上面的代码来的话,大概的思路就是以微博的几个大V为起始点,爬取他们各自用户信息、所发微博信息、他们各自的关注和粉丝列表。
如果想实现递归爬取,再获取关注和粉丝列表的用户信息、关注和粉丝列表,以此类推爬下去,只要把上面几行注释掉的加上即可
# 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)
# for fan in fans:
# if fan.get('user'):
# uid = fan.get('user').get('id')
# yield Request(self.user_url.format(uid=uid), callback=self.parse_user)
这样就会递归下去,如果一个用户与其他用户有社交网络上的关联,那他们的信息就会被爬虫抓取到,这样我们就可以做到对所有用户的爬取。
数据清洗
pipelines.py
import re, time
from Weibo.items import *
class TimePipeline():
def process_item(self, item, spider):
if isinstance(item, UserItem) or isinstance(item, WeiboItem):
now = time.strftime('%Y-%m-%d %H:%M', time.localtime())
item['crawled_at'] = now
return item
在Spider里没有对crawled_at 字段赋值,它代表爬取时间,我们可以统一将其赋值为当前时间,实现如上述中class TimePipeline( )
有些微博的时间可能不是标准的时间,比如它可能显示为刚刚、几分钟前、几小时前、昨天等。这里需要统一转化这些时间:
class WeiboPipeline():
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(time.time() - 24 * 60 * 60)) + ' ' + date
if re.match('\d{2}-\d{2}', date):
date = time.strftime('%Y-', time.localtime()) + date + ' 00:00'
return date
def process_item(self, item, spider):
if isinstance(item, WeiboItem):
if item.get('created_at'):
item['created_at'] = item['created_at'].strip()
item['created_at'] = self.parse_time(item.get('created_at'))
if item.get('pictures'):
item['pictures'] = [pic.get('url') for pic in item.get('pictures')]
return item
实现一个parse_time( )方法来转化时间,在process_item( )中进行处理。
数据储存
pipelines.py
在pipelines.py中还要进行一个重要步骤,数据清洗完毕之后,将数据保存到MongoDB数据库中
import pymongo
class MongoPipeline(object):
def __init__(self, local_mongo_host, local_mongo_port, mongo_db):
self.local_mongo_host = local_mongo_host
self.local_mongo_port = local_mongo_port
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
local_mongo_host=crawler.settings.get('LOCAL_MONGO_HOST'),
local_mongo_port=crawler.settings.get('LOCAL_MONGO_PORT'),
mongo_db=crawler.settings.get('DB_NAME')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.local_mongo_host, self.local_mongo_port)
# 数据库名
self.db = self.client[self.mongo_db]
# 以Item中collection命名 的集合(数据库表) 添加index
self.db[UserItem.collection].create_index([('id', pymongo.ASCENDING)])
self.db[WeiboItem.collection].create_index([('id', pymongo.ASCENDING)])
self.db[UserRelationItem.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
这里需要注意几点
1.open_ spider( )方法里添加了Collection 的索引,这里为Item 都添加了索引,索引的字段是id 。由于我们这次是大规模爬取,爬取过程涉及数据的更新问题,所以我们为每个Collection建立了索引,这样可以大大提高检索效率。
2.在process_item( )方法里存储使用的是update( )方法,第一个参数是查询条件,第二个参数是爬取的Item 。这里我们使用了$set 操作符,如果爬取到重复的数据即可对数据进行更新,同时不会删除已存在的字段。如果这里不加$set 操作符,那么会直接进行item 替换,这样可能会导致已存在的字段清空。第三个参数设置为True ,如果数据不存在,则插入数据。这样我们就可以做到数据存在即更新、数据不存在即插入,从而获得去重的效果。
3.对于用户的关注和粉丝列表,我们使用了一个新的操作符,叫作$addToSet ,这个操作符可以向列表类型的字段插入数据同时去重。它的值就是需要操作的字段名称。这里利用了$each操作符对需要插入的列表数据进行了遍历,以逐条插入用户的关注或粉丝数据到指定的字段。
middlewares.py
Cookies 池对接
# cookie池
class CookiesMiddleware(object):
"""
每次请求都随机从账号池中选择一个账号去访问
"""
def __init__(self):
client = pymongo.MongoClient(LOCAL_MONGO_HOST, LOCAL_MONGO_PORT)
self.account_collection = client[DB_NAME]['account']
def process_request(self, request, spider):
all_count = self.account_collection.find({'status': 'success'}).count()
if all_count == 0:
raise Exception('当前账号池为空')
random_index = random.randint(0, all_count - 1)
random_account = self.account_collection.find({'status': 'success'})[random_index]
request.headers.setdefault('Cookie', random_account['cookie'])
request.meta['account'] = random_account
弄了几个微博小号,将账号密码复制到 Weibo/account_build/account.txt中,格式与account_sample.txt保持一致
然后运行login.py ,由于有一些验证码识别困难,这里无验证码识别部分,添加了 点击登录后 等待10s时间,10s钟内自己手动点击验证码,账号 密码 cookie 会保存在mongodb中
settings.py
BOT_NAME = 'Weibo'
SPIDER_MODULES = ['Weibo.spiders']
NEWSPIDER_MODULE = 'Weibo.spiders'
ROBOTSTXT_OBEY = False
DEFAULT_REQUEST_HEADERS = {
'Accept': 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, deflate, sdch',
'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2,mt;q=0.2',
'Connection': 'keep-alive',
'Host': 'm.weibo.cn',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest',
}
DOWNLOADER_MIDDLEWARES = {
'Weibo.middlewares.CookiesMiddleware': 554,
'Weibo.middlewares.ProxyMiddleware': 555,
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': None,
}
ITEM_PIPELINES = {
'Weibo.pipelines.TimePipeline': 300,
'Weibo.pipelines.WeiboPipeline': 301,
'Weibo.pipelines.MongoPipeline': 302,
}
# 这个设置项的意思是遇到这些错误码就重新发送请求
RETRY_HTTP_CODES = [401, 403, 408, 414, 500, 502, 503, 504]
# MongoDb 配置
LOCAL_MONGO_HOST = '127.0.0.1'
LOCAL_MONGO_PORT = 27017
DB_NAME = 'mweibocn'
添加了IP代理池
用的这位老师的Github源码:01ly/FooProxy
对接到Scrapy中
import json
import logging
import requests
import random
# 代理ip
class ProxyMiddleware():
def __init__(self, proxy_url):
self.logger = logging.getLogger(__name__)
self.proxy_url = proxy_url
def get_random_proxy(self):
try:
# response = requests.get(self.proxy_url)
# if response.status_code == 200:
# proxy = response.text
response = requests.get(self.proxy_url)
if response.status_code == 200:
proxy_d = random.choice(json.loads(response.text))
ip = proxy_d.get('ip')
port = proxy_d.get('port')
proxy = ip + ':' + port
return proxy
except requests.ConnectionError:
return False
def process_request(self, request, spider):
# if request.meta.get('retry_times'):
proxy = self.get_random_proxy()
if proxy:
uri = 'https://{proxy}'.format(proxy=proxy)
self.logger.debug('使用代理 ' + proxy)
request.meta['proxy'] = uri
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings
return cls(
proxy_url=settings.get('PROXY_URL')
)
后来~ 试了一下阿布云的代理
爬取到的部分内容
通过这个api获取某个用户的粉丝数是有上限的,爬到人民日报的粉丝列表250页+后的api失效了(代码还是有问题!会中断,醉了~泪)
scrapy微博反爬虫_Scrapy 爬取新浪微博(解析api)相关推荐
- python爬虫微博粉丝数据_Scrapy 爬取新浪微博数据分析男女粉丝数量
通过之前爬取新浪微博发现,无论通过http://m.weibo.cn这里,还是http://weibo.cn这里,都没办法获取全部粉丝数据 那如果我想分析一个明星的男女粉丝数据怎么办,比如想知道某明星 ...
- python战反爬虫:爬取猫眼电影数据 (一)(Requests, BeautifulSoup, MySQLdb,re等库)
姓名:隋顺意 博客:Sui_da_xia 微信名:世界上的霸主 本篇文章未涉及猫眼反爬,主要介绍爬取无反爬内容,战反爬内容请去 python战反爬虫:爬取猫眼电影数据 (二)(Requests, Be ...
- python战反爬虫:爬取猫眼电影数据 (二)(Requests, BeautifulSoup, MySQLdb,re等库)
姓名:隋顺意 博客:Sui_da_xia 微信名:世界上的霸主 本文主要介绍破解反爬,可以先去上一篇观看爬取无反爬内容 python战反爬虫:爬取猫眼电影数据 (一)(Requests, Beauti ...
- python战反爬虫:爬取猫眼电影数据 (一)
非常荣幸邀请到 赛迪人工智能大赛(简称AI世青赛)全球总决赛银奖的获得者 隋顺意 小朋友为本公众号投稿,隋小朋友虽然小小年纪,但编程能力已经比大多数大学生强非常多了,欢迎大家关注,捧场. 姓名:隋顺意 ...
- scrapy微博反爬虫_基于Scrapy的微博爬虫设计
Data Base Technique • 数据库技术 Electronic Technology & Software Engineering 电子技术与软件工程 • 187 [关键词]Sc ...
- python爬取微博恶评_Python爬取新浪微博评论数据,了解一下?
开发工具 **Python版本:**3.6.4 相关模块: argparse模块: requests模块: jieba模块: wordcloud模块: 以及一些Python自带的模块. 环境搭建 安装 ...
- 克服反爬虫机制爬取智联招聘网站
一.实验内容 1.爬取网站: 智联招聘网站(https://www.zhaopin.com/) 2.网站的反爬虫机制: 在我频繁爬取智联招聘网站之后,它会出现以下文字(尽管我已经控制了爬虫的爬 ...
- 反反爬虫之--爬取大众点评--店铺名称、详址、经纬度、评价人数、平均消费等信息
every blog every motto: Let's be loyal to our ideals, let's face reality-Chegwara 前言: 知难不难! 折腾了几天爬取大 ...
- 【Python爬虫】爬取新浪微博评论看网友如何评价NBA季后赛火箭VS爵士G3
网友如何评论NBA季后赛火箭VS爵士G3 爬取网友评论 首先我们找到一篇关于比赛的微博 生成词云图 我们看看关键的几个人物:哈登,米切尔,塔克,徐坤(乱入?) 哈登 莫非今天又是常规操作30+?MVP ...
最新文章
- 实现 连续15签到记录_MySQL和Redis实现用户签到,你喜欢怎么实现?
- php sf框架,GitHub - YanCastle/sf: php swoole framework
- 关于MongoDB数据库的总结
- CSS基础(part11)--盒子模型之内边距
- 使用 Angular
- PL/SQL Developer调试Oracle存储过程
- linux mysql web界面吗_Linux下安装MySQL Web 管理工具phpMyAdmin
- DMA(direct memory access)直接内存访问
- BitmapFactory.decodeResource(res, id); 第一个参数跟第二个参数有什么关系?
- 愿你和我一样喜欢蛋炒饭
- 语法树,短语,直接短语,句柄
- 计算机绘图课程选用课本,机械制图与计算机绘图 机械制图与计算机绘图 机械制图与计算机绘图课程标准.doc...
- window下Python查看已经启动的进程名称并关闭
- Sharepreferences
- AtCoder Beginner Contest 214(补题)
- matlab中乘法“*”和点乘“.*”;除法“/”和点除“./”的联系和区别。
- JavaWeb Ajax二级联动Bootstrap的基本使用
- Containerd高级命令行工具nerdctl安装及使用
- Inspection info: This inspection analyzes method control and data flow to report possible conditions
- h5 canvas html 合成,图片合成以及canvas的应用