该文介绍自己爬取果壳精彩问答的内容
1、创建项目:scrapy startproject GuoKr
2、进入GuoKr目录下,以crawl 模板创建爬虫:scrapy genspider -t crawl guokr guokr.com
3、在item.py中定义爬取的数据的属性:问题、回答以及问答的链接
# 果壳问答中的问题属性question
question = scrapy.Field()
# 果壳问答中的回答属性answers
answers = scrapy.Field()
# 果壳问答中的链接属性
url = scrapy.Field()
4、爬取规则和页面处理 spider\guokr.py
(1)爬取的入口链接是https://www.guokr.com/ask/highlight/?page=1,精彩问答的问答列表是根据翻页进行展示的,所以要先爬取问答列表页,获取翻页的链接,根据列表页再爬取问答的详情页链接,根据详情页链接爬取详情页的数据,在详情页中解析需要的数据。
在crawl模版中,有rules规则,可以根据Rule()函数来对翻页和问答链接进行提取:
spider.guokr.py
rules = (
# 关于列表页的链接的提取,以跟进的方式提取到所有的列表页
Rule(LinkExtractor(allow=r'page=\d+'), follow=True),
# 关于问答详情页面链接的提取,不跟进详情页页面的链接提取,爬取到详情页面后,交与parse_item()函数处理
Rule(LinkExtractor(allow='question'), callback="parse_item", follow=False),
)
Rule()类中可以决定要爬取的链接匹配LinkExtractor,爬取链接后的回调函数callback以及是否要跟踪链接的提取。
Rule(self, link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=identity)
LinkExtractor类主要用于当前页面链接的过滤
LinkExtractor(self, allow=(), deny=(), allow_domains=(), deny_domains=(), restrict_xpaths=(), tags=('a', 'area'), attrs=('href',), canonicalize=False, unique=True, process_value=None, deny_extensions=None, restrict_css=(), strip=True)
allow字段是直接用的正则表达式进行匹配,符合要求的链接留下;deny则是不符合的留下;allow_domains则是符合匹配的域名的链接留下,这样可以避免跳到其他网站爬取不相关的页面;
栗子说明:
Rule(LinkExtractor(allow=r'page=\d+'), follow=True)
该规则表示当前页面中的列表页的链接提取和页面信息的处理,根据页面的规则来看,翻页的链接规则是page=\d+,如图1;follow=True则表示在新页面中若发现有链接符合page=\d+,也将该链接添加到爬取的链接中;如下,翻页的链接中都是在链接后添加参数page=数字,精彩问答总共有100页,如果follow=False的话,那爬虫就只爬取了入口页一页的问答列表,没有爬取2~100页的问答列表页,follow=True的话就会在入口页page=1的时候,获取page=2,3,4,5,6,7,8的翻页链接,在page=100的时候,获取page=93,94,95,96,97,98,99的列表页链接了。
Rule(LinkExtractor(allow='question'), callback="parse_item", follow=False)
该规则则是爬取了问答详情页,将详情页的数据发给parse_item()函数进行处理
(2)parse_item(self, response)函数对爬取到的详情页面进行数据的解析处理
def parse_item(self, response):
item = GuokrItem()
# 获取url
item['url'] = response.url
# 获取问题
item['question'] = response.xpath('//h1[@id="articleTitle"]/text()').extract_first()
# 获取问题的多个回答
item['answers'] = []
answers_data = response.css('.answer-txt')
for answer_data in answers_data:
texts = answer_data.css('p::text').extract()
answer = ''
for txt in texts:
if txt != ' ':
answer = answer + txt
item['answers'].append(answer)
yield item
问题question字段:
回答answers字段:
数据解析完成之后,返回数据给engine,由engine转发给管道pipeline对数据进行存储操作
5、数据存储
另外编写了mongoSave.mongoSave类将数据保存到mongo数据库
mongoSave.py
import pymongo
from scrapy.conf import settings
class mongoSave(object):
def __init__(self):
mongos = settings['MONGOS']
connection = pymongo.MongoClient(host=mongos['host'], port=mongos['port'])
database = connection[mongos['db']]
self.collection = database[mongos['collection']]
def process_item(self, item, spider):
if item['question'] != None:
self.collection.insert(dict(item))
return item
需要在settings配置文件中添加mongo配置
MONGOS = {
"host": "127.0.0.1",
"port": 27017,
"db": "study",
"collection": "guokr"
}
6、关于中间件middlewares.py的一些内容
(1)UserAgentMiddleware(object):每个请求中添加随机的user-agent
class UserAgentMiddleware(object):
def __init__(self):
self.user_agent_list = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36',
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52"
]
def process_request(self, request, spider):
# 随机选择一个user-agent,通过.headers["User-Agent"]添加进请求中
user_agent = random.choice(self.user_agent_list)
request.headers["User-Agent"] = user_agent
def process_response(self, request, response, spider):
# 查看请求头的User-Agent是否添加成功
user_agent = request.headers['User-Agent']
print(user_agent)
return response
user-agent的处理是在下载中间件中设置的,所以要在settings配置文件中的DOWNLOADER_MIDDLEWARES字段中添加该中间件:'GuoKr.middlewares.UserAgentMiddleware': 444
注:数字444只是代表中间件的权重
(2)ProxyMiddleware(object):使用代理进行请求
class ProxyMiddleware(object):
def process_request(self, request, spider):
proxy = "118.187.58.34:53281"
# 通过.meta['proxy']添加代理设置
request.meta['proxy'] = proxy
代理设置同样是在下载中间件中设置的,所以要在settings配置文件中的DOWNLOADER_MIDDLEWARES字段中添加该中间件:'GuoKr.middlewares.ProxyMiddleware': 333,
7、运行结果
8、遇到的问题
问题1:运行scrapy报错:ImportError: No module named win32api
原因:python找不到win32api模块
参考链接:
ImportError: no module named win32api
Python运行scrapy报错:ImportError: No module named win32api
问题2:spider.py文件中的 Rule(LinkExtractor(allow=r'page=\d+'), follow=True) 中,就算follow=True,也没有数据显示,通过print发现没有调用到parse_item()函数
原因:spider.py文件中,GuokrSpider类中的allowed_domains = ['guokr.com']误写成了'guokr.cn',导致爬取的url被过滤掉了
使用scrapy框架出现callback指定的函数不被调用的情况
问题3:问答的详情页数据有爬取到,但是engine没有调用到mongoSave中的process_item()函数进行数据的保存。
原因:一开始在spider.py的parse_item()函数中,使用了return item语句直接返回了item数据,但是engine需要的是生成器的数据,所以要使用yield item才可以
参考链接:
彻底理解Python中的yield
Scrapy----Item Pipeline的一个小问题
9、其他参考链接
Scrapy入门教程
scrapy笔记(3)将数据保存到mongo
小白进阶之Scrapy
完整代码