工作中需要抓百度贴吧指定贴吧每日新贴,包括主题贴和回复贴。
按理说百度应该给吧主提供这样的功能。但我没找到。百度还提供了个高级搜索功能 http://tieba.baidu.com/f/search/adv 。搜索结果可以按时间倒序排列,可惜必须设关键字或用户名。所以也不能满足需求。想了个变通的办法。1)每分钟爬一次首页,取到首页上最后回复者的用户名。2)使用高级搜索,设定吧名和用户名搜索并提取发贴日期为结果。[root@iZlmsymZ ~]$wget http://tieba.baidu.com/f?kw=%C9%BD%CE%F7 -O - | grep -Po 'href="(/p/[0-9]{10}).*?j_user_card.*?\>(.*?)\<.*?threadlist_reply_date j_reply_data.*?>[ ]*([0-9]{1,2}:[0-9]{2})' - | sed -r s/'href="(\/p\/[0-9]{10}).*?j_user_card[^>]*>([^<]*)<.*?threadlist_reply_date j_reply_data.*?>[ ]*([0-9]{1,2}:[0-9]{2})'/'\1\t\2\t\3'/g
wget -- linux 下获取 http页面的工具 -O - 表示输出到管道(而不是文件)
grep --linux 正则表达式工具
-P 使用 prel语法
-o 只输出匹配到的内容(而不是一行)
(/p/[0-9]{10}) 首页贴子的链接,格式为 a href="/p/3444444444"
.*? 非贪婪匹配任意字符 j_user_card.*?\>(.*?) 查找发贴人昵称,值为(.*?)
threadlist_reply_date j_reply_data.*?>[ ]*([0-9]{1,2}:[0-9]{2}) 最后回复日期,值为([0-9]{1,2}:[0-9]{2}) 首页中不能获取到最新回复贴的url。为了获取最新的帖子(无论是主题还是回复),需要以到 http://tieba.baidu.com/f/search/ures? 查询。查询条件是吧名和用户名。在找到的结果中,如果发贴时间匹配,那么就可以认为是刚才在首页查到的新贴,从而采集url.
wget http://tieba.baidu.com/f?kw=山西 -O - | grep -Po 'j_user_card.*?\>.*?\<' - | sed -r s/'j_user_card[^>]*>([^<]*)<'/'http:\/\/tieba\.baidu\.com\/f\/search\/ures\?ie=utf-8\&kw=山西\&un=\1'/g
j_user_card 是用户名所在的标签标识。用grep找到后,用sed提取并连接成url。结果如下:
http://tieba.baidu.com/f/search/ures?ie=utf-8&kw=山西&un=佛光普照五台山http://tieba.baidu.com/f/search/ures?ie=utf-8&kw=山西&un=不见风雨见彩虹http://tieba.baidu.com/f/search/ures?ie=utf-8&kw=山西&un=★冰月寒心★http://tieba.baidu.com/f/search/ures?ie=utf-8&kw=山西&un=小伟苦笑http://tieba.baidu.com/f/search/ures?ie=utf-8&kw=山西&un=小伟苦笑http://tieba.baidu.com/f/search/ures?ie=utf-8&kw=山西&un=领域神王http://tieba.baidu.com/f/search/ures?ie=utf-8&kw=山西&un=chenxin0357 ……
[root@iZ62jhlmZ ~]$wget http://tieba.baidu.com/f?kw=山西 -O - | grep -Po 'j_user_card.*?\>.*?\<' - | sed -r s/'j_user_card.*?>([^<]*)<'/'\/f\/search\/ures\?ie=utf-8\&kw=山西\&un=\1'/g | wget --base=http://tieba.baidu.com -i - -O - | grep -Po '' >urlcheck.txt
上面使用 "wget -i - -O -"打开sed 组织的url,并使用grep提取相关内容到 urlcheck.txt中。
grep -Po ''
在高级查询中查询到的记录,我们只取当天的。
[root@iZ62jhlmsZ ~]$grep "$(date --rfc-3339='date')"
所以,提取一个用户当天发贴的语句是:
wget http://tieba.baidu.com/f?kw=山西 -O - | grep -Po 'j_user_card.*?\>.*?\<' - | sed -r s/'j_user_card.*?>([^<]*)<'/'\/f\/search\/ures\?ie=utf-8\&kw=山西\&un=\1'/g | wget --base=http://tieba.baidu.com -i - -O - | grep -Po ".*? $(date --rfc-3339='date').*?<\/font>" | sed s/'^.*\(.*\)<\/a>.* \(.*\)<\/div>.* \(.*\)<\/font>.* .*\([-0-9: ]\{16\}\)<\/font>.*$'/'\1\t\2\t\3\t\4\t\5'/g
实际sed操作会出错,试着转成utf-8编码就正常了。
iconv --from-code=latin1 --to-code=utf-8
原因解释:
"sed的执行会和locale相关。比如说如果文件的编码是gbk的,而locale是utf-8,这时候你所有关于正则表达式的知识都是不起作用的。根据我的经验用sed处理有中文字符处理的文件,最好把locale设置成LC_ALL=C,进行处理,或者你能保证文件的编码和locale是一致的情况下。"
export LC_ALL=C
推荐使用 LC_ALL=C 因为转成utf-8会严重影响正则匹配的效率。
sed 的另一个问题是它不支持非贪婪匹配。所以在书写的时候费了很大周折。
另外还得删除重复的记录。
awk '!a[$0]{a[$0]=1;print "/f/search/ures?rn=10&ie=utf-8&kw=山西&un="$0}' ./tiebaapp/tiebaactiveuser.lst&& :>./tiebaapp/tiebaactiveuser.lst | wget -q --base=http://tieba.baidu.com -i - -O - | grep -Po ".*? $(date --rfc-3339='date').*?<\/font>" | iconv --from-code=latin1 --to-code=utf-8 | sed s/'^.*\(.*\)<\/a>.* \(.*\)<\/div>.* \(.*\)<\/font>.* .*\([-0-9: ]\{16\}\)<\/font>.*$'/'\1\t\2\t\3\t\4\t\5'/g