专栏首页程序员的碎碎念JS动态加载以及JavaScript void(0)的爬虫解决方案

JS动态加载以及JavaScript void(0)的爬虫解决方案

# Intro

对于使用JS动态加载, 或者将下一页地址隐藏为 JavaScriptvoid(0)的网站, 如何爬取我们要的信息呢?

本文以 Chrome浏览器为工具, 36Kr为示例网站, 使用 Json Handle 作为辅助信息解析工具, 演示如何抓取此类网站.

# Detail

Step 1. 按下 F12 或右键 检查进入开发者工具

Step 2. 选中Network一栏, 筛选 XHR请求

XHRXMLHttpRequest, 可以异步或同步返回服务器响应的请求, 并且能够以文本或者一个 DOM 文档的形式返回内容.

JSON是一种与XML在格式上很像, 但是占用空间更小的数据交换格式, 全程是 JavaScript Object Notation, 本文中的36Kr动态加载时获取到的信息就是JSON类型的数据.

网站为了节省空间, 加快响应, 常常没有对 JSON 进行格式化, 导致 JSON 的可读性差, 难以寻找我们要的信息.

我们通过右键打开获取到的 XHR 请求, 然后看看数据是怎样的

使用 Json Handle 后的数据可读性就很高了

Step 3. 分析 URL

结合上面的截图, 分析这条 URL

https://36kr.com/api/newsflash?column_ids=69&no_bid=false&b_id=126035&per_page=20&_=1530699384159

这中间有两个参数很容易可以知道它的用途, 第一个是 per_page=20, 第二个是 _=1530699384159第一个参数是我们每次滚动后可以获取到的信息条数, 第二个是时间戳

试着改第一个参数改为10, 可以看到条数就变为10了.

改为1000呢? 很遗憾, 最大值只有300. 换算下来, 就是最多允许爬 15 页

滑动了超过15页发现仍然有信息显示, 经过转换, 发现它的时间戳只是浏览网页生成的时间戳, 与内容无关

按了几个数字, 修改了 b_id的值, 发现内容确实发生了改变, b_id又是网站设定的规则, 无从入手

改了 no_bidtrue似乎没有变化, 接着修改了 column_id为70, 发现新闻的内容发生改变, 合理猜测这个应该是新闻标签的id.

至此, 我们已大致了解整个 URL 的含义

per_page 每次滑动可以获得的数据条目, 最大值为300 column_ids 新闻内容标签, 69为资本, 68为B轮后等 b_id 新闻集合的某种id 时间戳 记录当前的浏览时间

最后把原本的 URL 缩减为

https://36kr.com/api/newsflash?column_ids=69&no_bid=true&b_id=&per_page=300

舍弃了 b_id, 同时删去时间戳, 防止服务器发现每次接收到的请求时间都是一样的

经过测试, 上述的 URL 是可以获取信息的

Step 4. 开始爬虫

接下来的步骤与平时爬虫类似. 不同的是获取信息不再通过Xpath这些工具, 而是直接通过 JSON 取值

取值方式简单粗暴, 点击对应的内容就可以看路径了

接着用 scrapy shell工具测试下正确性, 然后就可以写代码了.

由于新闻来源隐藏在 description, 经过观察, 不难发现它的规律, 写一条正则获取即可, 如果结果为空, 则说明来源是36Kr

# Source Code

Spider

# -*- coding: utf-8 -*-
import scrapy
import json
import re
from scrapy import Request
from ..items import FinvestItem


class A36krSpider(scrapy.Spider):
    name = '36kr'
    allowed_domains = ['36kr.com']
    start_urls = ['https://36kr.com/api/newsflash?column_ids=69&no_bid=true&b_id=&per_page=300']

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
    }

    def start_request(self):
        yield Request(self.start_urls, headers=self.headers)

    def parse(self, response):
        item = FinvestItem()
        # 转化为 unicode 编码的数据
        sites = json.loads(response.body_as_unicode())

        src_pattern = re.compile('。((.*))')

        for i in sites['data']['items']:
            item['link'] = i['news_url']
            item['title'] = i['title']
            if src_pattern.search(i['description']) == None:
                item['source'] = "36Kr"
            else:
                item['source'] = src_pattern.search(i['description']).group(1)
            item['create_time'] = i['published_at']
            item['content'] = i['description']

            yield item

Pipeline

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import pymongo
import re
from scrapy.conf import settings


class FinvestPipeline(object):

    def __init__(self):
        """
        use for connecting to mongodb
        """
        # connect to db
        self.client = pymongo.MongoClient(host=settings['MONGO_HOST'], port=settings['MONGO_PORT'])
        # ADD if NEED account and password
        # self.client.admin.authenticate(host=settings['MONGO_USER'], settings['MONGO_PSW'])
        self.db = self.client[settings['MONGO_DB']]
        self.coll = self.db[settings['MONGO_COLL']]

    def process_item(self, item, spider):
        content = item['content']
        title = item['title']

        fin = re.compile(r'(?:p|P)re-?(?:A|B)轮|(?:A|B|C|D|E)+?1?2?3?轮|(?:天使轮|种子|首)轮|IPO|轮|(?:p|Pre)IPO')

        result = fin.findall(title)
        if(len(result) == 0):
            result = "未透露"
        else:
            result = ''.join(result)

        content = content.replace(u'<p>', u' ').replace(u'</p>', u' ').replace(u'\n\t', ' ').strip()
        # delete html label in content
        rule = re.compile(r'<[^>]+>', re.S)
        content = rule.sub('', content)


        item['content'] = content
        item['funding_round'] = result
        self.coll.insert(dict(item))
        return item

JSON Handle及抓取网站地址

下载地址: http://jsonhandle.sinaapp.com/

抓取网站:https://36kr.com/newsflashes


入门小白, 欢迎大家指出错误, 技术交流

GitHub项目地址

https://github.com/FesonX/finvest-spider

本文分享自微信公众号 - 程序员的碎碎念(gh_53e607dd4782)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 前端面试题(一)

    答:== 表示相等即仅仅比较两边变量的数值是否相等;=== 表示恒等,首先比较两边的变量数据类型是否相等,其次比较两边的变量的数值是否相等。

    benny
  • π框架之实战项目(代码分享)

    通过之前的学习,本文主要介绍一下实现用户的登录、注册等功能的接口代码,让大家通过小实战来感悟phalapi框架的神奇之处。(以下代码均可右滑) ? 获取参数规则...

    benny
  • 用AJAX实现二级分类联动

    今天学习到一个不错的内容,那就是分类联动。前面文章有说到ajax的基础知识,今天刚好强化一下所学内容。 ? 来看下效果: ? 当你选择了“地方网站”的时候就会自...

    benny
  • JS动态加载以及JavaScript void(0)的爬虫解决方案

    对于使用JS动态加载, 或者将下一页地址隐藏为JavaScript void(0)的网站, 如何爬取我们要的信息呢?

    FesonX
  • Leetcode-Easy 728. Self Dividing Numbers

    728. Self Dividing Numbers 描述: 判断一个数是否可以被自己所包含的数整除,如果本身还有0,则不可以 ? 思路: ...

    致Great
  • Java数据解析---PULL

    听着music睡
  • 基于Scala Trait的设计模式

    在《作为Scala语法糖的设计模式》中,我重点介绍了那些已经融入Scala语法的设计模式。今天要介绍的两个设计模式,则主要与Scala的trait有关。 Dec...

    张逸
  • 携手品高云 互联港湾云网络战略全面升级

    港湾人儿
  • SwiftUI 如何管理一组TextField(零基础教程)

    知识大胖
  • WordPress <= 4.9.6 任意文件删除漏洞

    该漏洞出现的原因是由于在 WordPress 的wp-includes/post.php文件中wp_delete_attachement()函数在接收删除文件参...

    张维龙

扫码关注云+社区

领取腾讯云代金券