Spider是什么?
• Spider是一个Scrapy提供的基本类,Scrapy中包含的其他基本类(例如CrawlSpider)以及自定义的spider都必须继承这个类。
• Spider是定义如何抓取某个网站的类,包括如何执行抓取以及如何从其网页中提取结构化数据。
源码如下:
所有爬虫的基类,用户定义的爬虫必须从这个类继承
class Spider(object_ref):
#name是spider最重要的属性,而且是必须的。一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite
name = None
#初始化,提取爬虫名字,start_ruls
def __init__(self, name=None, **kwargs):
#判断是否存在爬虫名字name,没有则会报错
if name is not None:
self.name = name
elif not getattr(self, 'name', None):
raise ValueError("%s must have a name" % type(self).__name__)
# python对象或类型通过内置成员__dict__来存储成员信息
self.__dict__.update(kwargs)
#判断是否存在start_urls列表,从列表中获取到页面的URL开始请求,后续的URL将会从获取到的数据中提取。
if not hasattr(self, 'start_urls'):
self.start_urls = []
# Scrapy执行后的日志信息
def log(self, message, level=log.DEBUG, **kw):
log.msg(message, spider=self, level=level, **kw)
# 判断对象object的属性是否存在,不存在则做断言处理
def set_crawler(self, crawler):
assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % crawler
self._crawler = crawler
@property
def crawler(self):
assert hasattr(self, '_crawler'), "Spider not bounded to any crawler"
return self._crawler
@property
def settings(self):
return self.crawler.settings
#该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response
#注意:该方法仅调用一次
def start_requests(self):
for url in self.start_urls:
# 生成Request对象的函数
yield self.make_requests_from_url(url)
#Request对象默认的回调函数为parse(),提交的方式为get
def make_requests_from_url(self, url):
return Request(url, dont_filter=True)
#默认的Request对象回调函数,处理返回的response。
#生成Item或者Request对象。用户需要自己重写该方法中的内容
def parse(self, response):
raise NotImplementedError
@classmethod
def handles_request(cls, request):
return url_is_from_spider(request.url, cls)
def __str__(self):
return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))
__repr__ = __str__
因此可以总结出Scrapy爬取数据的过程如下:
Spider的入口方法(start_requests())请求start_urls列表中定义的url,返回Request对象(同时默认传给它一个名为parse的回调函数)。
下载器获取Respose后,回调函数会解析Reponse,返回(yield)的结果可能是字典、Item或是Request对象,亦或是这些对象组成的可迭代类型。其中,返回的Request也会包含一个回调函数,并在被下载之后被回调函数处理(即重复第2步)。
解析数据可以使用Scrapy自带的Selectors工具或者lxml、BeautifulSoup等模块。
最后Scrapy将返回的数据字典(或是Item对象)保存为文件或者保存在数据库中。
scrapy.spider.Spider类介绍
常用类属性
• name:是字符串。标识了每一个spider的名字,必须定义且唯一。实际中我们一般为每个独立网站创建一个spider。
• starturl:是包含初始请求页面url的列表,必须定义。`startrequests()方法会引用该属性,发出初始的Request`。
• custom_settings:是一个字典,每一条键值对表示一个配置,可用于覆写SETTINGS(Scrapy的全局配置模块,位于settings.py文件中)。
•
– 例1:custom_settings = {'COOKIES_ENABLED': True,'ROBOTSTXT_OBEY': False}。覆盖了全局属性COOKIES_ENABLED。
– 扩展:设置settings中的值的几种方法,优先级从高到低如下:
命令行选项
custom_settings
settings.py文件
命令行的默认设置,每一个命令行都有它自己的默认设置
默认的全局设置,被定义在 scrapy.settings.default_settings 中
• allowed_domains:是一个字符串列表。规定了允许爬取的网站域名,非域名下的网页将被自动过滤。
•
– 例1:allowed_domains = cnblogs.com,start_url = 'https://www.zhihu.com'。在这个例子中,知乎不属于CSDN的域名,因此爬取过程中会被过滤。
• crawler:是一个Crawler对象。可以通过它访问Scrapy的一些组件(例如:extensions, middlewares, settings)。
•
– 例1:spider.crawler.settings.getbool('xxx')。这个例子中我们通过crawler访问到了全局属性。
• settings:是一个Settings对象。它包含运行中时的Spider的配置。这和我们使用spider.crawler.settings访问是一样的。
• logger:是一个Logger对象。根据Spider的name创建的,它记录了事件日志。
常用方法
• start_requests:该方法是Spider的入口方法。默认下,该方法会请求start_url中定义的url,返回对应的Request,如果该方法被重写,可以返回包含Request(作为第一个请求)的可迭代对象或者是FormRequest对象,一般POST请求重写该方法。
• parse:当其他的Request没有指定回调函数时,用于处理下载响应的默认回调,主要作用:负责解析返回的网页数据(response.body),提取结构化数据(生成item)生成需要下一页的URL请求。。该方法用于编写解析网页的具体逻辑(包含解析数据,或是解析出新的页面),所以此方法非常重要哦!。
Spider案例:披荆斩棘的哥哥评论
最近被披荆斩棘的哥哥所吸引,但是还是要为大家做好服务,每天更新文章啊!介绍下这个综艺节目哈。
《披荆斩棘的哥哥》是芒果TV推出的全景音乐竞演综艺。节目嘉宾们彼此挑战,披荆斩棘,通过男人之间的彼此探索、家族建立的进程,诠释“滚烫的人生永远发光”,见证永不陨落的精神力。
我们本次使用Scrapy爬取哥哥们的评论。
分析思路:
打开谷歌浏览器,访问第01期的链接(https://www.mgtv.com/b/367750/13107580.html),把JavaScript加载关掉,刷新,发现底下的评论数据没有了,说明这数据是异步加载的,在这个网页链接的源代码里是找不到评论数据的;
既然是异步加载,那么就要抓包了。把刚刚关掉的JavaScript打开,重新加载网页,右键检查,Network, 数据一般都在XHR或者JS里面,所以先把这两项勾选了,这时候点击评论的下一页,发现数据就在JS里面:
由上面评论的真实链接可以知道,评论真实的请求网址是:“https://comment.mgtv.com/v4/comment/getCommentList?”,后面跟着一系列的参数(callback, _support, subjectType, subjectId, page, _),可见:
我们知道page是页码数,subjectId是s每个视频对应的id,callback回调函数,最后一个大胆猜测下就是unix时间戳后面再加上3位随机数(或者unix时间戳乘以1000再取整),应该只起一个占位的作用,可能是一个完全没用的参数,只是用来吓唬我们的。
但是不确定,我们来看一下,于是我去掉最后一个参数在浏览器发出了一下请求,结果如下:
说明就是一个完全没用的参数,哈哈哈用来吓唬我们的,不要怕!我们不用它。
链接有了之后我们就开始创建爬虫项目啦!
首先打开命令行,输入:
scrapy startproject mongotv_comments_crawler
生成新的mongotvcommentscrawler项目,再输入:
cd mongotv_comments_crawler
scrapy genspider mgtv_crawl mgtv.com
生成爬虫名。
然后,用PyCharm打开项目。由于最后爬取到的是json数据,我们直接解析Json数据,并返回到Items中。
因此在爬虫文件mgtv_crawl.py的MgtvCrawlSpider类中,进行如下定义:
class MgtvCrawlSpider(scrapy.Spider):
name = 'mgtv_crawl'
allowed_domains = ['mgtv.com']
# start_urls = ['http://mgtv.com/'] 因为我们每次都需要构建芒果TV的请求,所以我们重写start_requests方法
subject_id = 4327535 # 视频的id
pages = list(range(1, 100)) # 需要爬取的评论页数比如100页
因为我们要爬取多页的内容,所以我们要不断修改page参数,所以我们重写start_requests方法
def start_requests(self): # 重写start_requests
start_urls = [f'https://comment.mgtv.com/v4/comment/getCommentList?page={page}&subjectType=hunantv2014&subjectId={self.subject_id}&callback=jQuery18204988030991528978_1630030396693&_support=10000000&_=1630030399968' for page in self.pages]
# 生成所有需要爬取的url保存进start_urls
for url in start_urls: # 遍历start_urls发出请求
yield Request(url)
然后重写parse()函数,获取json结果。但是json结果前面有下图一样的前缀内容,我们要去掉
def parse(self, response):
text = response.text[response.text.find('{'):-1] # 通过字符串选取的方式把"jQuery...()去掉"
json_data = json.loads(text) # 转换成json格式
for i in json_data['data']['list']: # 遍历每页的评论列表
item = MongotvCommentsCrawlerItem()
item['content'] = i['content']
item['commentId'] = i['commentId']
item['createTime'] = i['createTime']
item['nickName'] = i['user']['nickName']
yield item
编写item,获取评论的:内容、创建时间、用户名和评论ID
class MongotvCommentsCrawlerItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
content = scrapy.Field()
createTime = scrapy.Field()
nickName = scrapy.Field()
commentId = scrapy.Field()
然后便是写pipelines.py文件,把爬取回来的items入库
import pymysql
class MongotvCommentsCrawlerPipeline(object):
def __init__(self):
self.conn = pymysql.connect(host='127.0.0.1', user='root', password='root',
db='mgtv', charset='utf8')
def process_item(self, item, spider):
commentId = item["commentId"]
content = item['content']
createTime = item['createTime']
nickName = item["nickName"]
sql = "insert into comments(commentId,content,createTime,nickName) values(" + str(commentId) + ",'" + content + "','" + createTime + "','" + nickName + "');"
self.conn.query(sql)
self.conn.commit()
return item
def close_spider(self, spider):
self.conn.close()
在settings.py中开启对应的设置项:
开启爬虫进行爬取:
scrapy crawl mgtv_crawl
爬取到的结果如下: