CSDN 已下载资源自动批量评论脚本

2014/10/12 Python

背景

CSDN 账号过一段时间就会累积几十个下载过但是未评论打分的资源,虽然现在上传了一些资源供别人下载后基本不愁积分,但是为了可持续发展,还是把评论一下就能顺手拿了的这种积分不客气地收入囊中吧!不过手动一个一个去评论真的很蛋疼……特别是 CSDN 还搞了个两个评论间隔不能小于 60 秒的限制,评论几十个就得至少花个几十分钟折腾,所以想想这种耗时、无脑的活还是交给程序来完成吧。

对于这类模拟 HTTP 请求然后可能频繁用到页面解析和正则表达式之类的活,用 C++ 写还是有点蛋疼的,用我那半生不熟的 Python 练练手正合适。

遂在 github 上建了个仓库开工,地址在这里:https://github.com/mzlogin/csdncommenter

分析

使用 Fiddler 把登录 - 到待评论页面 - 评论的完整流程抓了一下,整理程序逻辑大致如下:

注:如下 HTTP 请求均使用同一个 SESSION。

  1. 手动输入 CSDN 的用户名和密码。

  2. GET方法从 https://passport.csdn.net/account/login 页面获取ltexecution_eventId等参数。

  3. 将第 1 步中的用户名和密码,还有第 2 步中得到的参数POST给 https://passport.csdn.net/account/login ,从 Response 中判断是否登录成功——我采用的依据是 status_code 为 200 且 Reponse 内容中有lastLoginIP

  4. GET方法从 http://download.csdn.net/my/downloads 页面获取已下载资源总页数。从最后一个pageliststyhref中得到。

  5. 根据第 4 步中得到的总页数,根据每个页面 num 拼得 url 为 http://download.csdn.net/my/downloads/num ,使用GET方法访问之拿到该页面中所有待评论资源 ID。从所有class="btn-comment"a标签的href中得到。

  6. 对第 5 步中得到的所有待评论资源 ID 依次进行间隔至少 60S 的打分评论,随机打出 1 到 5 星,对应一句英文短句评论。出乎我意料的是评论这一步竟然也是用GET就可以做, http://download.csdn.net/index.php/comment/post_comment 后面带上sourceidcontent(评论内容)、rating(打分)和t(时间戳)参数就可以。评论成功会返回({"succ":1}),失败会返回「两次评论需要间隔 60 秒」、「您已经发表过评论」等之类的msg

最终运行截图如下:

CSDN 自动批量打分评论

确认这种方式能有效拿到 CSDN 的分数:

CSDN 自动评论得分

总结

  1. 用 Python 干这种类型的活还是很有优势的,requests 和 BeautifulSoup 简直神器啊!
  2. 我那点蹩脚的 Python 底子之所以能还比较顺利地把这个流程写下来,实际上也得亏 CSDN 对请求的验证相对较松,比如像我代码里那样写,User-Agent是带有Python字样的,而且很显然不是浏览器在访问,但 CSDN 并未对此作限制。

源码

没有找到从 Github Pages 引用 Github 仓库里的源码的方法,所以把 py 文件放到一个 gist 里了,引用如下

(Gist 前几天被伟大的墙封了,还是直接贴上代码吧。2014/11/5 update)

(GitHub 仓库:mzlogin/csdncommenter,现在可以通过 pip 安装使用了 pip install csdncommenter 然后 csdncommenter。2015/10/27 update)

# auto comment csdn resources
# File   : CsdnCommenter.py
# Author : Zhuang Ma
# E-mail : ChumpMa(at)gmail.com
# Website: http://www.mazhuang.org
# Date   : 2014-10-12
import requests
from BeautifulSoup import BeautifulSoup
import getpass
import time
import random
import re
import urllib

class CsdnCommenter():
    """Csdn operator"""
    def __init__(self):
        self.sess = requests.Session()

    def login(self):
        """login and keep session"""
        username = raw_input('username: ')
        password = getpass.getpass('password: ')
        url = 'https://passport.csdn.net/account/login'
        html = self.sess.get(url).text
        soup = BeautifulSoup(html)

        lt = self.getElementValue(soup, 'name', 'lt')
        execution = self.getElementValue(soup, 'name', 'execution')
        _eventId = self.getElementValue(soup, 'name', '_eventId')

        data = {
                'username' : username,
                'password' : password,
                'lt' : lt,
                'execution' : execution,
                '_eventId' : _eventId
                }

        response = self.sess.post(url, data)

        return self.isLoginSuccess(response)

    def autoComment(self):
        """main handler"""
        if self.getSourceIds() is False:
            print 'No source can comment!'
            return

        print 'Total %d source(s) wait for comment.' % len(self.sourceids)

        nhandled = 0
        for sourceid in self.sourceids:
            left = len(self.sourceids) - nhandled

            sec = random.randrange(61,71)
            print 'Wait %d seconds for start. %s source(s) left.' % (sec, left)
            time.sleep(sec)

            self.comment(sourceid)
            nhandled += 1

        print 'Finished!'

    def getSourceIds(self):
        """get source ids wait for comment"""
        self.sourceids = set()
        pagecount = self.getPageCount()
        if pagecount == 0:
            return False

        print 'Pagecount is %d.' % pagecount

        pattern = re.compile(r'.+/(\d+)#comment')

        for n in range(1, pagecount + 1):
            url = 'http://download.csdn.net/my/downloads/%d' % n
            html = self.sess.get(url).text
            soup = BeautifulSoup(html)
            sourcelist = soup.findAll('a', attrs={'class' : 'btn-comment'})
            if sourcelist is None:
                continue
            for source in sourcelist:
                href = source.get('href', None)
                if href is not None:
                    rematch = pattern.match(href)
                    if rematch is not None:
                        self.sourceids.add(rematch.group(1))

        return len(self.sourceids) > 0

    def getPageCount(self):
        """get downloaded resources page count"""
        url = 'http://download.csdn.net/my/downloads'
        html = self.sess.get(url).text
        soup = BeautifulSoup(html)

        pagelist = soup.findAll('a', attrs={'class' : 'pageliststy'})
        if pagelist is None:
            return 0

        lasthref = pagelist[len(pagelist) - 1].get('href', None)
        if lasthref is None:
            return 0
        return int(filter(str.isdigit, str(lasthref)))

    def comment(self, sourceid):
        """comment per source"""
        print 'sourceid %s commenting...' % sourceid
        contents = [
                'It just soso, but thank you all the same.',
                'Neither good nor bad.',
                'It is a nice resource, thanks for share.',
                'It is useful for me, thanks.',
                'I have looking this for long, thanks.'
                ]
        rating = random.randrange(1,6)
        content = contents[rating - 1]
        t = '%d' % (time.time() * 1000)

        paramsmap = {
                'sourceid' : sourceid,
                'content' : content,
                'rating' : rating,
                't' : t
                }
        params = urllib.urlencode(paramsmap)
        url = 'http://download.csdn.net/index.php/comment/post_comment?%s' % params
        html = self.sess.get(url).text
        if html.find('({"succ":1})') != -1:
            print 'sourceid %s comment succeed!' % sourceid
        else:
            print 'sourceid %s comment failed! response is %s.' % (sourceid, html)

    @staticmethod
    def getElementValue(soup, element_name, element_value):
        element = soup.find(attrs={element_name : element_value})
        if element is None:
            return None
        return element.get('value', None)

    @staticmethod
    def isLoginSuccess(response):
        if response.status_code != 200:
            return False
        return -1 != response.content.find('lastLoginIP')

if __name__ == '__main__':
    csdn = CsdnCommenter()
    while csdn.login() is False:
        print 'Login failed! Please try again.'
    print 'Login succeed!'

    csdn.autoComment()

Search

    Post Directory