最近搞了个简单的文章转视频上传到youtube,需要采集点文章来生成视频,所以打算用Scrapy采集点今日头条的文章,目标是抓取其中一个分类的几十篇文章内容和下载其中的图片导出json数据文件用来生成视频,简单做一下记录。

打开头条网站,有多个分类,打开娱乐版块,查看源代码发现是没有文章内容,看来是js加载的,F12查看分析找到文章数据的接口链接
https://www.toutiao.com/api/pc/feed/?category=news_entertainment&utm_source=toutiao&widen=1&max_behot_time=0&max_behot_time_tmp=0&tadrequire=true&as=A1B5FBF1A7CA537&cp=5B172A5593E7AE1&_signature=oNvqZwAA-9u3ZjcQBbvTJ6Db6n
在浏览器里直接打开这个链接,很好,返回json结构的数据,刷新一下,返回不同的文章,嗯,这么easy,一个链接就搞定了。

那还等什么,新建Scrapy项目抓一下试试,哈哈,什么也没有,看一下Scrapy的输出 DEBUG: Forbidden by robots.txt 原来是头条禁止爬虫抓取了,需要设置Scrapy的参数 ROBOTSTXT_OBEY = False,再试一下还是不行,直接返回

{
  "message": "error",
  "data": [],
  "has_more": false
}

没有文章数据,构造header加上User-Agent跟Referer

headers = {
        "Referer": "https://www.toutiao.com/ch/news_entertainment/",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"
    }

OK,正常返回数据了,多次请求,嘿,返回的文章基本都一样,看来还是不可以哦,再加上cookie试试,用固定的 tt_webid=6563786091257824776;,多次请求,发现跟浏览器里刷新是一样的了,会返回不同的文章,倒是基本可以满足一次抓取几十篇文章需求了。

不过既然搞了那再深入研究一下看看这几个参数是干啥的嘛

category=news_entertainment 分类,对应头条的分类,news_entertainment是娱乐

utm_source=toutiao 固定

widen=1 固定

max_behot_time=0max_behot_time_tmp=0值相同,是个时间戳,初次请求是0,每次请求返回的数据中都会带有下一次请求的时间戳 "next": {"max_behot_time": 1528358256}

tadrequire=true带广告,false不带广告

as=A1B5FBF1A7CA537cp=5B172A5593E7AE1是简单的时间加密,网上搜一下,很容易找到python的翻译代码,不加这两个参数,也能返回文章数据

def get_as_cp():
    t = int(math.floor(time.time()))
    e = hex(t).upper()[2:]
    m = hashlib.md5()
    m.update(str(t).encode(encoding='utf-8'))
    i = m.hexdigest().upper()
    if len(e) != 8:
        as_key = "479BB4B7254C150"
        cp_key = "7E0AC8874BB0985"
        return as_key, cp_key
    n = i[:5]
    a = i[-5:]
    r = ""
    s = ""
    for k in range(5):
        s += n[k]+e[k]
        r += e[k+3]+a[k]

    as_key = "A1" + s + e[-3:]
    cp_key = e[0:3] + r + "E1"

    return as_key, cp_key

_signature=oNvqZwAA-9u3ZjcQBbvTJ6Db6n 是个签名,测试发现这个参数值的生成跟浏览器UA有关,换个UA就没有数据返回了,_signature跟UA匹配才会返回数据,网上倒是没有找到python的翻译代码,看来这个有点难搞,先用固定值,构造链接

url = 'https://www.toutiao.com/api/pc/feed/?category=news_entertainment&utm_source=toutiao&widen=1' \
          '&max_behot_time={0}' \
          '&max_behot_time_tmp={0}' \
          '&tadrequire=true' \
          '&as={1}' \
          '&cp={2}' \
          '&_signature=oNvqZwAA-9u3ZjcQBbvTJ6Db6n'

发现第一次请求max_behot_time=0时正常返回文章数据,之后带上新的时间戳后,就没有数据了,看来这个签名还跟时间戳有点关系,找一下js代码看看,

function e(t) {
        var e = ascp.getHoney(),
            i = "";
        window.TAC && (i = TAC.sign("refresh" === t ? 1 : r.params.max_behot_time_tmp)), r.params = _.extend({}, r.params, {
            as: e.as,
            cp: e.cp,
            max_behot_time: "refresh" === t ? 0 : r.params.max_behot_time_tmp,
            _signature: i
        })
    }

果然,签名的生成跟max_behot_time_tmp的值有关,貌似是用TAC.sign()函数生成,找一下这个函数看看,emmm~,乱码~,尴尬了,在F12控制台敲入TAC.sign,发现还是乱码是什么鬼,这个函数还搞了一下加密?算了,算了,大道至简,还是搜搜看有没有人搞过吧,不过没有找到python的翻译代码,让我自己写,哈哈,那还是写不出来的,不费这个劲了,直接用解析js的方式来获取这个值吧,搜了一下,可以用pyExecJS这个模块来解析js,但是直接把头条的js文件加载进来发现会报错,不过倒是找到一份整理过的js代码 toutiao-TAC.sign.txt,https://bbs.125.la/thread-14115046-1-1.html,感谢nliger2015的分享,可以直接拿来用没有报错。

def get_as_cp_signature(max_behot_time=0):
    with open('toutiao-TAC.sign.txt', encoding='utf-8') as f:
        js = f.read()
    ctx = execjs.compile(js)
    s = ctx.call('get_as_cp_signature', max_behot_time)
    return json.loads(s)

哈哈,很好,可以直接返回as,cp,_signature三个参数值,不错不错,之前as和cp的翻译函数不需要了,获取文章列表数据基本差不多了,接下来开始搞文章详情。

先是直接用xpath获取内容,哈哈,结果取回来的全是None,嗯?xpath写的不对?放到浏览器测试没问题的,查看源代码看一下,我去,详情页的内容也是用js加载的,想要的内容都在js里呢,都放到BASE_DATA这个变量里了,是json结构的对象,不过不能直接用json模块解析,因为不全是用的双引号,json模块解析会报错,唉,好吧,既然在js里,那直接用js解析一下,要是能直接返回BASE_DATA这个变量对象,不就是拿到了文章的结构数据,用起来不就更省事了,不就是想怎么用就怎么用了,哈哈。

data = response.xpath('//script[contains(text(),"var BASE_DATA")]/text()').extract_first()
    if data:
        data = data + "\nvar a = function(){return BASE_DATA;}"
        ctx = execjs.compile(data)
        data = ctx.call('a')
        print(data['articleInfo']['title'])

先用xpath取出需要的js代码部分,再加个函数return这个变量,然后用pyExecJS解释执行一下,返回文章详情数据,执行下来,倒是没问题,也正常返回数据了,而且确实是直接可以拿来用的,不过输出一下,中文全变成了“锟斤拷锟斤拷锟叫★拷锟斤拷锟斤拷钱锟斤...”,这是什么鬼?编码有问题?尝试转码后再传递给js执行,结果报错,emmm,什么鬼,各种编码解码尝试无果,放弃js解析,改用正则,哈哈,还是正则好用,指哪拿哪

data = response.xpath('//script[contains(text(),"var BASE_DATA")]/text()').extract_first()
data = re.search(r"title:\s*'(.*?)',\s*content:\s*'(.*?)',[\s\S]*?tags:\s*(\[.*\]),", data)
if data:
    title, content, tags = data.group(1, 2, 3)

直接拿到title,content,tags的数据,再对拿到的数据进行一下处理,加个下载图片的pipeline把图片也下载下来,嗯,基本搞定,采集导出的json数据文件可以直接用来生成视频。

还有点小问题,重复文章数据,因为写这个小爬虫,就是采集文章用来生成视频的,不过生成视频比较慢,嗯是挺慢的,所以一次采集个几十篇文章即可,在一次抓取中,返回的文章数据是没有重复的,不过下一次再重新启动爬虫抓取时,就会跟上次采集的文章有重复的,时间间隔不长的话,重复的还不少,也加上cookie固定ttweb_id的值试了下发现效果不大,一样会有重复的数据,所以就把每次抓取的文章链接存到文件里,每次开始抓取前,先读入存放链接的文件把已经抓取的链接放到列表里,每次发送文章详情页request前先判断一下是不是抓取过,在列表中的链接直接跳过,等到抓取完成爬虫关闭时,再把新抓取的链接追加到文件里,等抓取的链接比较多的时候,这种方式可能不太好,不过没关系,满足当前的需求就可以了,哈哈。

标签: scrapy, 爬虫

仅有一条评论

  1. 大佬您好,我在用php请求头条的数据,请问php应该怎么设置cookie呢 每次返回的数据一样

添加新评论