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)相关推荐

  1. python爬虫微博粉丝数据_Scrapy 爬取新浪微博数据分析男女粉丝数量

    通过之前爬取新浪微博发现,无论通过http://m.weibo.cn这里,还是http://weibo.cn这里,都没办法获取全部粉丝数据 那如果我想分析一个明星的男女粉丝数据怎么办,比如想知道某明星 ...

  2. python战反爬虫:爬取猫眼电影数据 (一)(Requests, BeautifulSoup, MySQLdb,re等库)

    姓名:隋顺意 博客:Sui_da_xia 微信名:世界上的霸主 本篇文章未涉及猫眼反爬,主要介绍爬取无反爬内容,战反爬内容请去 python战反爬虫:爬取猫眼电影数据 (二)(Requests, Be ...

  3. python战反爬虫:爬取猫眼电影数据 (二)(Requests, BeautifulSoup, MySQLdb,re等库)

    姓名:隋顺意 博客:Sui_da_xia 微信名:世界上的霸主 本文主要介绍破解反爬,可以先去上一篇观看爬取无反爬内容 python战反爬虫:爬取猫眼电影数据 (一)(Requests, Beauti ...

  4. python战反爬虫:爬取猫眼电影数据 (一)

    非常荣幸邀请到 赛迪人工智能大赛(简称AI世青赛)全球总决赛银奖的获得者 隋顺意 小朋友为本公众号投稿,隋小朋友虽然小小年纪,但编程能力已经比大多数大学生强非常多了,欢迎大家关注,捧场. 姓名:隋顺意 ...

  5. scrapy微博反爬虫_基于Scrapy的微博爬虫设计

    Data Base Technique • 数据库技术 Electronic Technology & Software Engineering 电子技术与软件工程 • 187 [关键词]Sc ...

  6. python爬取微博恶评_Python爬取新浪微博评论数据,了解一下?

    开发工具 **Python版本:**3.6.4 相关模块: argparse模块: requests模块: jieba模块: wordcloud模块: 以及一些Python自带的模块. 环境搭建 安装 ...

  7. 克服反爬虫机制爬取智联招聘网站

    一.实验内容 1.爬取网站: 智联招聘网站(https://www.zhaopin.com/) 2.网站的反爬虫机制:     在我频繁爬取智联招聘网站之后,它会出现以下文字(尽管我已经控制了爬虫的爬 ...

  8. 反反爬虫之--爬取大众点评--店铺名称、详址、经纬度、评价人数、平均消费等信息

    every blog every motto: Let's be loyal to our ideals, let's face reality-Chegwara 前言: 知难不难! 折腾了几天爬取大 ...

  9. 【Python爬虫】爬取新浪微博评论看网友如何评价NBA季后赛火箭VS爵士G3

    网友如何评论NBA季后赛火箭VS爵士G3 爬取网友评论 首先我们找到一篇关于比赛的微博 生成词云图 我们看看关键的几个人物:哈登,米切尔,塔克,徐坤(乱入?) 哈登 莫非今天又是常规操作30+?MVP ...

最新文章

  1. 实现 连续15签到记录_MySQL和Redis实现用户签到,你喜欢怎么实现?
  2. php sf框架,GitHub - YanCastle/sf: php swoole framework
  3. 关于MongoDB数据库的总结
  4. CSS基础(part11)--盒子模型之内边距
  5. 使用 Angular
  6. PL/SQL Developer调试Oracle存储过程
  7. linux mysql web界面吗_Linux下安装MySQL Web 管理工具phpMyAdmin
  8. DMA(direct memory access)直接内存访问
  9. BitmapFactory.decodeResource(res, id); 第一个参数跟第二个参数有什么关系?
  10. 愿你和我一样喜欢蛋炒饭
  11. 语法树,短语,直接短语,句柄
  12. 计算机绘图课程选用课本,机械制图与计算机绘图 机械制图与计算机绘图 机械制图与计算机绘图课程标准.doc...
  13. window下Python查看已经启动的进程名称并关闭
  14. Sharepreferences
  15. AtCoder Beginner Contest 214(补题)
  16. matlab中乘法“*”和点乘“.*”;除法“/”和点除“./”的联系和区别。
  17. JavaWeb Ajax二级联动Bootstrap的基本使用
  18. Containerd高级命令行工具nerdctl安装及使用
  19. Inspection info: This inspection analyzes method control and data flow to report possible conditions
  20. h5 canvas html 合成,图片合成以及canvas的应用

热门文章

  1. win7和xp系统下的防火墙配置例外
  2. nginx 反向代理(完成图片回显示效果)
  3. 关于Visual Studio订阅(原MSDN订阅)中无法激活Office 365权益的解决方法(仅适用于MVP)
  4. 每日一分享C语言代码(The first day)
  5. 研究表明:菜鸟爱用右脑,专家爱用左脑!
  6. mysql workbench安装教程_MySql可视化工具MySQL Workbench使用教程
  7. 蓝牙(六)HFP协议
  8. 填补空缺——压缩感知
  9. 利盟linux驱动下载,利盟Lexmark X342n 驱动下载
  10. 预测算法——指数平滑法