详解简书用户信息采集——crawlspider&itemloader

爬取简书全站用户信息其实并不是特别容易,其中有个小坎就是A、B两用户互相关注,爬取时很容易造成死循环,这样的死循环会越爬越多越爬越多,像雪球一样越滚越大,造成爬取效率聚减,群内交流有说先入库再去重其实是一样的,当你入库去重完毕后会发现入库30W条,去重后只有5W条了,这都是由互相关注所造成的,下面给出策略图、爬虫代码、代码注释,仅供参考。(翻到最后有惊喜)

策略图

策略图

一、策略讲解

1、策略入口

1、策略入口

红圈位置是策略图入口,个人认为这些用户都是大咖所以从这入手是再好不过的,里面有好几页AJX的用户推荐名单,需要拼接和循环去获取用户KEY_ID,将用户关注页面分为2层,上层是用户基本信息,下层是用户关注信息,新建一个空集合,将爬取过基本信息用户的KEY_ID存入集合,然后将下层关注信息遍历取出其他用户KEY_ID,然后让去集合去判断是否爬去过该KEY_ID,最后构筑这些用户信息的关注页面的链接做循环操作。

2、左击关注

点击查看全部之后推荐用户的界面,点击红圈


2、左击关注

3、取得用户URL

红圈中的字符串是 图2 用户的KEY_ID,爬取该用户基本信息,将该KEY_ID放入集合(敲黑板!!!注意这里最好放集合,尽量不要用列表,用列表放入列表中的KEY_ID也都是重复的,不会去重,用集合可以节约开销!),然后看url后面following是关注页面的后缀,所以之后只需要将KEY_ID + following就可以构筑出关注页面


3、取得用户URL

4、取得关注页面

这里需要注意红圈的地方,如果关注用户超过10个,有ajx动态加载的,需要去重新构筑URL遍历用户获取关注的用户KEY_ID
URL是这样的 http://www.jianshu.com/users/7e54016a5a06/following?page=1 自己去重构,不会看下面代码,获取完用户KEY_ID然后用这些KEY_ID去集合中去重,如果不在集合里则拿出来构筑following关注页面,就这样再回到第一步。

4、取得关注页面

二、代码讲解

Scrapy / python3.5

spider

# -*- coding: utf-8 -*-
import scrapy
import re
from urllib.parse import urljoin
from scrapy.http import Request
from article.items import JianShuItem, JianShuItemLoader


class JianshuSpider(scrapy.Spider):
    name = "jianshu"
    allowed_domains = ["www.jianshu.com"]
    url_top_half = "http://www.jianshu.com"
    start_urls = []
    used_id = set()
    # 设置一个空集合存爬过的用户
    for pg in range(1, 11):
        # 第一步将推荐作者的链接放入start_url
        start_users_url_join = "http://www.jianshu.com/recommendations/users?page=" + str(pg)
        start_urls.append(start_users_url_join)

    def parse(self, response):
        start_user_list = response.css("div.wrap a.avatar::attr(href)").extract()
        # 获取推荐作者的href
        if start_user_list:
            # 起始页面判断,循环完start_url就没用了
            # 判断推荐作者href是否为空,空则爬完推荐作者,不进入if判断
            for k_1 in start_user_list:
                # 取得href格式为"/users/"+key
                url_second_half1 = k_1 + "/following"
                # k_1是列表循环出来未经清洗的字符串格式为"/users/"+key 连接上"/following"
                user_following_url1 = urljoin(self.url_top_half, url_second_half1)
                yield Request(url=user_following_url1, callback=self.parse_kernel)
                # 核心解析函数

    def parse_kernel(self, response):
        # 既然解析item 又解析关注用户
        reg_key = re.compile(r"http://www.jianshu.com/users/(.*)/following")
        key_2 = reg_key.findall(str(response.url))[0]
        # 获取key
        if key_2 not in self.used_id:
            # 判断key是否使用过,阻断互相关注循环
            item_loader = JianShuItemLoader(item=JianShuItem(), response=response)
            item_loader.add_css("key_id", "div.main-top a.name::attr(href)")
            item_loader.add_css("user_name", "div.main-top a.name::text")
            item_loader.add_css("contracted", ".main-top span.author-tag::text")
            item_loader.add_xpath("follow", ".//div[@class ='info']/ul/li[1]//p/text()")
            item_loader.add_xpath("fans", ".//div[@class ='info']/ul/li[2]//p/text()")
            item_loader.add_xpath("article", ".//div[@class ='info']/ul/li[3]//p/text()")
            item_loader.add_xpath("words_count", ".//div[@class ='info']/ul/li[4]//p/text()")
            item_loader.add_xpath("get_likes", ".//div[@class ='info']/ul/li[5]//p/text()")
            jianshu_item_loader = item_loader.load_item()
            self.used_id.add(key_2)
            # 将用过的key,放入集合
            yield jianshu_item_loader

        follow_num = response.xpath(".//div[@class ='main-top']//li[1]//p/text()").extract()[0]
        # 获取该用户关注人数
        follow_pg = round(int(follow_num)/9)
        # AJX每个页面有9个关注
        if follow_pg != 0:
            # 关注数不为0的进入if判断
            for f_pg in range(1, follow_pg+1):
                # 获取该用户关注人数的页
                user_following_url_pg = response.url + "?page=" + str(f_pg)
                yield Request(url=user_following_url_pg, callback=self.parse_following)
                # 将每一页的用户关注人的url回调给parse_following

    def parse_following(self, response):
        following_list = response.css("ul.user-list a.avatar::attr(href)").extract()
        # 解析出被关注用户的key
        for k_3 in following_list:
            key_3 = k_3.replace("/u/", "")
            # 清洗出key
            url_second_half3 = "/users/{}/following".format(key_3)
            user_following_url3 = urljoin(self.url_top_half, url_second_half3)
            # 直接拼接following url,返回给parse_kernel,进入循环
            yield Request(url=user_following_url3, callback=self.parse_kernel)

item

# -*- coding: utf-8 -*-
import re
import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose, TakeFirst, Join
from w3lib.html import remove_tags
from article.settings  import SQL_DATETIME_FORMAT, SQL_DATE_FORMAT
import datetime

# 以下为简书用户数据清洗函数
def key_id_filter(value):
    return value.replace("/u/", "")

def contracted_filter(value):
    if value == ' 签约作者':
        return value.strip()
    elif value == "":
        return "未签约"


class JianShuItemLoader(ItemLoader):
    default_output_processor = TakeFirst()
    # itemloader提取默认为list,所以这里需要重筑这个类的默认值


class JianShuItem(scrapy.Item):
    key_id = scrapy.Field(
        input_processor=MapCompose(key_id_filter)
    )
    user_name = scrapy.Field()
    contracted = scrapy.Field(
        input_processor=MapCompose(contracted_filter)
    )
    follow = scrapy.Field()
    fans = scrapy.Field()
    article = scrapy.Field()
    words_count = scrapy.Field()
    get_likes = scrapy.Field()

pipeline

# 常规写法拿去改下参数就可以用了
class JianShuMongodbPipeline(object):
    def __init__(self):
        client = pymongo.MongoClient(
            host="localhost",
            port=27017,
        )
        db = client["jianshu"]
        self.coll = db["user_info"]

    def process_item(self, item, spider):
        self.coll.insert(dict(item))
        return item

以上是个人见解,如有错误或更优解欢迎交流~


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 源码加翻译 #import <Foundation/NSArray.h> #import <Foundation/...
    CAICAI0阅读 1,141评论 0 50
  • 一. Java基础部分.................................................
    wy_sure阅读 3,778评论 0 11
  • width: 65%;border: 1px solid #ddd;outline: 1300px solid #...
    邵胜奥阅读 4,751评论 0 1
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,146评论 1 23
  • 家乡味其实就是,当你小的时候那种味蕾已深深的植入你的胃里,不一定别人喜欢,但你绝对执迷。其中油圪垛就是老家特有的产...
    3a87c8175f0a阅读 7,717评论 21 24