本来按照计划这篇文章是要讲一讲Scrapy框架中的Spider Middleware,后来有个学金融的朋友说要我帮忙抓取下和讯论坛中通过关键字搜索正文后结果中所有的的帖子内容,发帖人,以及发帖的时间,刚好最近在学这个,拿来练练手,这种利人利己的事情,何乐而不为呢。
整个实现思路很简单,废话不多说,直接上代码:
# -*- coding: utf-8 -*-
import re
import scrapy
import urllib
from Hexun_Forum.items import HexunForumItem
# from scrapy.shell import inspect_response
class HonglingSpider(scrapy.Spider):
name = "hongling"
allowed_domains = ["bbs.hexun.com"]
@staticmethod
def __remove_html_tags(str):
return re.sub(r'<[^>]+>', '', str)
def start_requests(self):
# keywords = getattr(self, 'keywords', None)
# '网站的编码是gb2312的'
keywords = u'红岭'.encode('gb2312')
requesturl = "http://bbs.hexun.com/search/?q={0}&type=2&Submit=".format(urllib.quote(keywords))
return [scrapy.Request(requesturl, meta={'dont_obey_robotstxt ': True}, callback=self.__parse_blog_pages)]
def __parse_blog_pages(self, response):
# '解析跳转到每篇文件链接'
# for blog_url in response.xpath('//tr[@class="bg"]/td[@class="f14"]/a[@class="f234"]/@href').extract():
for blog_url in response.css('tr[class=bg] td[class=f14] a[class=f234] ::attr(href)').extract():
yield scrapy.Request(blog_url, meta={'dont_obey_robotstxt ': True}, callback=self.parse)
# '解析跳转到搜索结果的下一页'
# inspect_response(response, self)
# next_page = response.xpath('//div[@class="pagenum"]/a[@class="next"]/@href').extract_first()
next_page = response.css('div[class=pagenum] a[class=next]::attr(href)').extract_first()
if next_page is not None:
requesturl = "http://bbs.hexun.com{0}".format(next_page)
yield scrapy.Request(requesturl, meta={'dont_obey_robotstxt ': True}, callback=self.__parse_blog_pages)
def parse(self, response):
# '解析博客的标题,内容和时间'
# inspect_response(response, self)
# author = response.xpath('//div[@class="arttitle"]/strong/text()').extract_first()
author = response.css('div[class=arttitle] strong::text').extract_first()
# '这个论坛正文中,还有很多东西需要删除'
# content = response.xpath('//div[@class="txtmain"]').extract_first()
content = response.css('div[class=txtmain]').extract_first()
# '删除文章结束后的"查看更多精彩内容请进论坛首页"等相关多余内容'
content = re.sub(u'div style=[\w\W]+', '', content).strip()
# '时间格式形如: 发表于: [2016-12-24 13:30:42] ,后续会去除多余字符'
# time = response.xpath('//h3/span/text()').extract_first()
time = response.css('h3 span::text').extract_first()
yield HexunForumItem({
'author': author,
# '去掉html标签'
'content': HonglingSpider.__remove_html_tags(content),
# '去掉 多余的 "发表于:[] 等字符"'
'time': re.sub(u'[\w\W]*\[', '', re.sub(u']', '', time)).strip()
})
代码在Python 2.7.5
下测试可以用。通过下面两种方式可分别保存json和csv两种格式:
scrapy crawl hongling -s FEED_EXPORT_ENCODING=utf-8 -o hongling.json
scrapy crawl hongling -s FEED_EXPORT_ENCODING=utf-8 -o hongling.csv
论坛里面的帖子比较多,保存下来的结果比较大,这里就不贴出,感兴趣的可以自己运行下代码。整个工程代码会在文章结尾处贴出。
下面就实现过程中所遇到的个人觉得值得记录的问题进行一个简单的记录,以便以后翻阅。
刚开始没多想,因为GET参数,跟POST不一样,GET参数是直接加在在URL后面直接传送的,所以我觉得直接在要抓取的URL中写死GET参数即可,如这样:
http://bbs.hexun.com/search/?q=aaaa&type=2&Submit=
这个就是在和讯论坛中通过关键字—-aaaa
搜索正文
的url链接地址(包括GET参数)。最简单的得到这个地址的方法就是通过浏览器访问这个博客的地址,然后在搜索输入框中输入要搜索的关键字,选择搜索类型为正文,然后点击搜索,浏览器弹出来的网页的地址栏里就是我们上面这个地址,如下图所示:
如果有人想要刨根问底,想知道上面地址中q
,type
,Submit
这几个关键字的含义,那就只能看网页源码(要学爬虫还是要会点html的语法):
<div class="glay" id="glay">
<div id="gCon1_1">
<label>
<input type="text" onclick="InMsg(0)" value="输入关键词" class="inpsty1 gray" name="q" id="sw"/>
label>
<label>
<select name="type">
<option value="1">标题option>
<option value="2">正文option>
<option value="16">作者option>
select>
label>
<input type="submit" class="btnsty1" title="搜索" value="" name="Submit">
div>
div>
说的这么多貌似有点偏题,上面说到在URL中写死GET参数,如把我们上面的链接地址直接通过Scrapy shell来访问验证下,如下:
[james_xie@james-desk python]$ scrapy shell "http://bbs.hexun.com/search/?q=xxxx&type=2&Submit=" --nolog
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler
[s] item {}
[s] request
[s] response <200 http://bbs.hexun.com/search/?q=xxxx&type=2&Submit=>
[s] settings
[s] spider
[s] Useful shortcuts:
[s] shelp() Shell help (print this help)
[s] fetch(req_or_url) Fetch request (or URL) and update local objects
[s] view(response) View response in a browser
>>>
一定要注意链接地址得用引号括起来,否则会出错
上面能进入Scrapy的交互模式的命令行,说明是可行的。
通过上面问题1我确认了GET参数可以在URL中写死访问,但是我们要搜索的关键字是中文—红岭
,就得涉及到对中文编解码的问题。
Python的urllib库中的如下两函数,可以满足对URL编解码需求:
urllib.quote(string[, safe])
Replace special characters in string using the %xx escape. Letters, digits, and the characters '_.-' are never quoted. By default, this function is intended for quoting the path section of the URL. The optional safe parameter specifies additional characters that should not be quoted — its default value is '/'.
Example: quote('/~connolly/') yields '/%7econnolly/'.
urllib.unquote(string)
Replace %xx escapes by their single-character equivalent.
Example: unquote('/%7Econnolly/') yields '/~connolly/'.
这里还要注意网站的编码,刚开始没注意网站编码,在这纠结半天,在网页源码头文件中发现其编码是gb2312
的。
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
# keywords = getattr(self, 'keywords', None)
# '网站的编码是gb2312的'
keywords = u'红岭'.encode('gb2312')
requesturl = "http://bbs.hexun.com/search/?q={0}&type=2&Submit=".format(urllib.quote(keywords))
最后通过上面的两句话既可实现,这样做相对比上面直接访问灵活很多,还可以修改通过Scrapy的参数形式把要搜索的关键字以参数传入。
刚开始在抓取的时候,一直有如下信息:
2016-12-27 11:14:39 [scrapy] INFO: Spider opened
2016-12-27 11:14:39 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-12-27 11:14:39 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6026
2016-12-27 11:14:40 [scrapy] DEBUG: Crawled (200) http://bbs.hexun.com/robots.txt> (referer: None)
2016-12-27 11:14:40 [scrapy] DEBUG: Forbidden by robots.txt: http://bbs.hexun.com/search/?q=%BA%EC%C1%EB&type=2&Submit=>
2016-12-27 11:14:40 [scrapy] INFO: Closing spider (finished)
2016-12-27 11:14:40 [scrapy] INFO: Dumping Scrapy stats:
显然爬虫程序应该是被拒绝了,我通过HttpFox
和wireshark
抓包对比发现我爬虫程序发出的包和正常通过浏览器发出的包基本没什么区别,我也通过scrapy-fake-useragent库来伪装user agent字段,按理来说不会被限的。
后来Google了一下,发现居然有robots
协议。下面这段话摘自百度词条:
Robots协议(也称为爬虫协议、机器人协议等)的全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。
之后在Scrapy的文档中发现有对robots
协议的控制的支持。
ROBOTSTXT_OBEY
Default: False
Scope: scrapy.downloadermiddlewares.robotstxt
If enabled, Scrapy will respect robots.txt policies. For more information see RobotsTxtMiddleware.
Note
While the default value is False for historical reasons, this option is enabled by default in settings.py file generated by scrapy startproject command.
这里就不做翻译,可以自己去查阅下官方文档—RobotsTxtMiddleware
整个实现过程相对比较简单,刚开始的时候,被其搜索出来的结果量给吓到,因为有几十页,然后每一页又有几十篇,后续实现中发现只有通过Scrapy抓取的内容正确,内容多少都没关系,只是爬虫程序运行的时间长短问题。最后整个代码放在网上—-通过Scrapy抓取和讯论坛关键字搜索的结果 ,感兴趣的可以下载讨论下,欢迎拍砖!