Hello World
Spiga

抓取InfoQ内容的calibre脚本

2013-06-30 23:11 by 老赵, 9097 visits

两个礼拜前我公开了一个抓取今年MSDN Magazine内容的calibre脚本,这次则是针对InfoQ的。最近用Kindle Paperwhite看书一发不可收拾,自然想要更好地利用这个设备。InfoQ是一个难得的高质量站点,可惜它的RSS只输出摘要,甚至只有前十条内容,让人感到十分不方便。但这显然难不住calibre这个电子书管理神器和伟大的程序员,于是我这段时间又断断续续地编写了InfoQ站点内容的抓取脚本,各个方面细节感觉修饰地都还算不错,特此公布。至于这个脚本该怎么用,就请自行看下calibre的帮助吧。

为了更好的使用这段脚本,我这里也再稍微谈下部分细节。这个脚本最一开始是一些配置:

language = 'en'

site_url = 'http://www.infoq.com/'

title_prefix = 'InfoQ'

date_regexes = [
    r'Jan\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Feb\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Mar\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Apr\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'May\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Jun\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Jul\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Aug\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Sep\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Oct\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Nov\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'Dec\s+(?P<day>\d{2}),\s+(?P<year>\d{4})'
]

这些代码记录了抓取目标的一些信息,包括用来匹配日期用的正则表达式。显然这些配置的目标是InfoQ英文主站,假如要针对InfoQ中文站,则只需将下面这段注释去掉即可:

language = 'zh'

site_url = 'http://www.infoq.com/cn/'

title_prefix = 'InfoQ中国站'

date_regexes = [
    r'一月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'二月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'三月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'四月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'五月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'六月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'七月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'八月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'九月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'十月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'十一月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'十二月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})'
]

再接下来则是配置网站中哪些部分的内容是需要抓取的,目前InfoQ有三个主要的文字部分,分别是“新闻(news)”,“文章(articles)”和”访谈(interviews)“,您可以只保留需要抓取的部分,每个部分会作为这本书的一个“章节”来对待。

# sections to download
sections = [ 'news', 'articles', 'interviews' ]

最后则是每个部分所需要抓取的时间范围,每个时间范围均为一个二元tuple,表示起始日期和终止日期,方便起见均为“闭区间”,换句话说假如您要抓取当天的内容,则使用(date.today(), date.today())即可。除了统一的时间范围之外,您还可以为不同的部分指定不同的时间范围:

# the range of date (both inclusive) to download
date_range = (date(2013, 6, 20), date(2013, 6, 22))

# the range of date to override for sections
section_date_ranges = {
    # 'news: (date(2013, 6, 21), date(2013, 6, 22)),
    # 'articles': (date(2013, 6, 5), date(2013, 6, 10)),
    # 'interviews': (date(2013, 1, 1), date(2013, 3, 1))
}

至于剩下的部分,我就不多解释了,总之就是分析页面,获取每一篇文章的基本数据,生成索引脚本calibre的框架即可。还有一部分代码是用来从一张页面的DOM信息中挑选出可读的部分,顺便我必须吐槽,InfoQ改版后的HTML还是那么糟糕,对Readability的支持还是很差,只能由我进行自定义分析了,且分析起来也很麻烦。总之,假如您不知道这些代码在作什么,建议您不要轻易改动。当然,代码其实很简单,感兴趣的话也不妨试着理解一下那是最好的了。

我是这么用这个脚本的:首先,我抓取了最近半年来所有的内容放着慢慢回顾,当然只仔细看那些感兴趣的部分,大部分时间是刷刷地翻,这比直接阅读InfoQ网页效率高出何止一星半点。然后,保留一个脚本,每天早上定时抓取前一天发布的内容(使用date.today() - timedelta(days = 1)作为时间范围的上下限),并使用Amazon的文档服务推送到Kindle设备上阅读。

我抓取了两个mobi格式的示例文档(中英文各一),感兴趣的朋友可以下载来看看,可以直接用PC/Mac/iOS/Android上的Kindle阅读器打开,也可以使用calibre自带的或其他mobi文档阅读器,这里我只贴出Kindle Paperwhite的截图。

主索引:

Main Index 主索引

块状索引:

Block Index (0) Block Index (1) 块状索引(0) 块状索引(1)

列式索引:

List Index 列式索引

新闻内容:

News Content (0) News Content (1) 新闻内容(0) 新闻内容(1)

访谈内容:

Interview Content (0) Interview Content (1) Interview Content (2) 访谈内容(0) 访谈内容(1) 访谈内容(2)

Kindle Paperwhite实在是个好东西,绝对推荐大家购买。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

42 条回复

  1. 链接

    jun1st 2013-07-03 16:49:17

    自己还正想写一个这方面的!

    非常好用,Thanks

  2. edwinxu
    219.142.118.*
    链接

    edwinxu 2013-07-04 11:13:32

    改一改抓抓你的博客试试...

  3. 老赵
    admin
    链接

    老赵 2013-07-05 15:03:59

    @edwinxu

    我的博客很实在,都在RSS里……

  4. 老赵
    admin
    链接

    老赵 2013-07-07 22:25:34

    话说某苍蝇同学,无关话题我隐藏了,无数人说被你这傻逼搞的头大,只能加上个隐藏功能,只给准备好的同学看。

    您登陆了看看,我继续陪您吵,别说不爱登陆,留下来痕迹来死不了人,别停啊。

  5. cdjboy
    222.161.198.*
    链接

    cdjboy 2013-07-12 19:44:28

    赵帅,有什么好方法:递归一个树级结构的category来生成一样树形结构的category?

  6. 老赵
    admin
    链接

    老赵 2013-07-13 16:14:53

    @cdjboy

    这么基本的编程题,过了那么多年你还不会啊……

  7. cdjboy
    120.128.6.*
    链接

    cdjboy 2013-07-14 14:04:55

    会呀,像这样的,只是改造一下就行了。

    private static List<RecursiveObject> FillRecursive(List<FlatObject> flatObjects, int parentId)
    {
        List<RecursiveObject> recursiveObjects = new List<RecursiveObject>();
    
        foreach (var item in flatObjects.Where(x => x.ParentId.Equals(parentId)))
        {
            recursiveObjects.Add(new RecursiveObject
            {
                Data = item.Data,
                Id = item.Id,                   
                Children = FillRecursive(flatObjects, item.Id)
            });
        }
    
        return recursiveObjects;
    }
    

    只是觉得代码不美。所以问有什么好办法。

  8. 老赵
    admin
    链接

    老赵 2013-07-14 17:01:44

    @cdjboy

    你觉得什么样的代码叫做美?

  9. cdjboy
    120.128.6.*
    链接

    cdjboy 2013-07-14 19:55:29

    呃,无力回答了。感觉美就像柏拉图所说的理念,你能感觉出来……好吧,遁隐……

  10. 老赵
    admin
    链接

    老赵 2013-07-14 23:23:48

    @cdjboy

    总之,假如你的代码实现了你的需求的话(就是说,不是你绕弯路上的一个环节),我觉得可以接受啊,意图挺清晰的。

  11. 链接

    新泽 李 2013-07-16 09:10:18

    额 我想抓取2012年至今的所有中文内容于是将配置部分改为

    language = 'zh'
    site_url = 'http://www.infoq.com/cn/'
    title_prefix = 'InfoQ中国站'
    date_regexes = [
    r'一月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'二月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'三月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'四月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'五月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'六月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'七月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'八月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'九月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'十月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'十一月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})',
    r'十二月\s+(?P<day>\d{2}),\s+(?P<year>\d{4})'
    ]
    
    # the sections to download
    sections = [ 'news', 'articles', 'interviews' ]
    # the range of date (both inclusive) to download
    date_range = (date(2012, 1, 1), date.today())
    # the range of date to override for sections
    section_date_ranges = {
    # 'news': (date(2013, 6, 21), date(2013, 6, 22)),
    # 'articles': (date(2013, 6, 5), date(2013, 6, 10)),
    # 'interviews': (date(2013, 1, 1), date(2013, 3, 1))
    } 
    

    Error:

    Python function terminated unexpectedly 
      day is out of range for month (Error Code: 1)
    Traceback (most recent call last):  
      File "site.py", line 132, in mainFile "site.py", line 109, in run_entry_point
      File "site-packages\calibre\utils\ipc\worker.py", line 189, in main
      File "site-packages\calibre\gui2\convert\gui_conversion.py", line 25, in gui_convert
      File "site-packages\calibre\ebooks\conversion\plumber.py", line 1023, in run
      File "site-packages\calibre\customize\conversion.py", line 241, in __call__
      File "site-packages\calibre\ebooks\conversion\plugins\recipe_input.py", line 114, in convert
      File "site-packages\calibre\web\feeds\news.py", line 980, in download
      File "site-packages\calibre\web\feeds\news.py", line 1145, in build_indexFile "<string>", line 193, in parse_index
      File "<string>", line 174, in get_items
      File "<string>", line 104, in parse_date
    ValueError: day is out of range for month
    
  12. 老赵
    admin
    链接

    老赵 2013-07-16 10:26:52

    @新泽 李

    把评论代码和错误信息格式整理干净再说。

  13. 链接

    新泽 李 2013-07-16 10:40:25

    额 错误提示部分我加过代码标记但是莫名的没生效
    另外评论者主页链接这个字段的长度限制会导致用谷歌账户登录时给出的链接超出长度限制的说

  14. 老赵
    admin
    链接

    老赵 2013-07-16 13:32:35

    @新泽 李

    我现在没法调试,要不你先看看log?

  15. 链接

    新泽 李 2013-07-16 13:39:57

    最后这句:

    ValueError: day is out of range for month
    

    是不是说日期超界了?

    Log好长,似乎无论我设置的日期范围是什么都会从当前日期开始遍历,在到达2013/01/01就会报错我设置为20130102至今也是会报错但是不是在抓取新闻,抓取新闻完成后,开始抓取文章然后到达20130102就报错了。

    2013年7月16日14:03:45 Update

    日期设置为20130103就可以开始抓取文章了 估计还是日期转换出现问题了

  16. 老赵
    admin
    链接

    老赵 2013-07-26 23:19:27

    @新泽 李

    看不懂……把你用的设置给我看下?

  17. 链接

    新泽 李 2013-07-27 10:41:37

    什么设置? 抓取的配置我已经在2013-07-16 01:10:18的评论里面贴出来了啊.

  18. BlueIceQ
    118.186.8.*
    链接

    BlueIceQ 2013-08-02 14:52:40

    请问老赵,你文中说的使用Amazon的文档服务推送到Kindle设备上,是需要自己发送电子书到kindle上,还是calibre自己就有功能了呢?

  19. BlueIceQ
    118.186.8.*
    链接

    BlueIceQ 2013-08-02 16:04:51

    搞懂了,在首选项-分享-通过Email分享书籍,添加邮件地址里面写的是Amazon的邮箱,下面写自己的邮箱地址。不过奇怪的是,我用的126的邮箱,好像没有设置用户名、密码,就可以发送邮件了。

  20. 链接

    新泽 李 2013-08-19 11:46:55

    额 这么长时间也没个回应啊... 就程序的输出来看,似乎是日期的计算出现了问题,只要设置了早于2013.1.1的时间问题就会出现,应该是2013年1月1日之后没有对年做减法而是跑到2013年1月0日去了,我对于python完全是一窍不通,看了半天代码连程序的运行流程都没弄清楚.

  21. Ninja
    127.0.0.*
    链接

    Ninja 2013-10-13 22:15:34

    正好路过你的博客,又正好在下calibre这个软件(据说能专mobi的).原来这软件还有这么强大的功能!!Try it

  22. bench1
    111.192.247.*
    链接

    bench1 2013-11-17 22:20:47

    请问老赵,怎么在kindle上阅读github上的项目啊??google没有答案呢..

  23. Rusher
    183.37.62.*
    链接

    Rusher 2014-01-09 14:08:29

    @新泽 李

    今天正好要用到,打了下日志,发现会出现2月31这样的日期。

  24. Rusher
    183.37.62.*
    链接

    Rusher 2014-01-09 14:24:51

    '十二月' 正则表达式匹配到了 '二月',然后12月31日就成了2月31日,日期转化的时候就报错了。

  25. 老赵
    admin
    链接

    老赵 2014-01-10 11:22:27

    @Rusher

    嗯,那么修改下正则吧,加点\b啊\w什么的,或者直接把十二月提前,呵呵。

  26. 雪飞
    124.74.44.*
    链接

    雪飞 2014-07-14 15:28:45

    老赵,我加导入你的recipe代码后,下载时报错如下,不知道什么问题。 OS: ubuntu12.04

    Traceback (most recent call last):
      File "/usr/bin/calibre-parallel", line 19, in <module>
        sys.exit(main())
      File "/usr/lib/calibre/calibre/utils/ipc/worker.py", line 191, in main
        result = func(*args, **kwargs)
      File "/usr/lib/calibre/calibre/gui2/convert/gui_conversion.py", line 25, in gui_convert
        plumber.run()
      File "/usr/lib/calibre/calibre/ebooks/conversion/plumber.py", line 960, in run
        accelerators, tdir)
      File "/usr/lib/calibre/calibre/customize/conversion.py", line 204, in __call__
        log, accelerators)
      File "/usr/lib/calibre/calibre/web/feeds/input.py", line 105, in convert
        ro.download()
      File "/usr/lib/calibre/calibre/web/feeds/news.py", line 858, in download
        res = self.build_index()
      File "/usr/lib/calibre/calibre/web/feeds/news.py", line 1002, in build_index
        feeds = feeds_from_index(self.parse_index(), oldest_article=self.oldest_article,
      File "/tmp/calibre_0.8.38_tmp_XPkUwJ/qaqB67_recipes/recipe0.py", line 200, in parse_index
        if date > end: continue
    TypeError: can't compare datetime.date to NoneType
    
  27. 老赵
    admin
    链接

    老赵 2014-07-14 18:38:02

    @雪飞

    是上面那个问题吗?

  28. 雪飞
    124.74.44.*
    链接

    雪飞 2014-07-15 10:33:11

    是的。 打开“calibre library”后,导入你的recipe,然后创建了"定期新闻下载",然后点击“自定义”---“InfoQ中国站”---“立即下载”之后就会跳出个窗口,报错信息就是上面的那些。

  29. 雪飞
    124.74.44.*
    链接

    雪飞 2014-07-15 10:36:11

    另外,我把如下的代码注释掉后,下载的时候有些成功,有些不成功。 # if date > end: continue # if date < begin: break

  30. Kenny
    61.130.114.*
    链接

    Kenny 2015-03-20 08:58:59

    recipes要如何处理有用户名密码的网页呢?

  31. Kenny
    61.130.114.*
    链接

    Kenny 2015-03-20 09:00:43

    Calibre 的recipes要如何处理有用户名密码的网页呢?最近定了一本电子杂志,想把它下载到KPW看,毕竟电脑看眼镜太累了,但不知在recipes里面如何处理有用户名密码的网页

  32. xioxu
    123.139.112.*
    链接

    xioxu 2016-01-10 11:53:17

    哇哦, 之前真不知道这个软件, 两者配合好强大, 老赵文中代码稍旧了些, 日期、内容转换部分代码需要修改, 需要的可以直接下载: https://github.com/xioxu/calibre-recipes/blob/master/InfoQ.py

已自动隐藏某些不合适的评论内容(主题无关,争吵谩骂,装疯卖傻等等),如需阅读,请准备好眼药水并点此登陆后查看(如登陆后仍无法浏览请留言告知)。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我