Scrapy—轻量级爬虫框架(二)
本文作者:徐露露
文字编辑:张梦婷
技术总编:刘洪儒
有问题,不要怕!点击推文底部“阅读原文”下载爬虫俱乐部用户问题登记表并按要求填写后发送至邮箱statatraining@163.com,我们会及时为您解答哟~
爬虫俱乐部的github主站正式上线了!我们的网站地址是:https://stata-club.github.io,粉丝们可以通过该网站访问过去的推文哟~
好消息:爬虫俱乐部隆重推出数据定制及处理业务啦,您有任何网页数据获取及处理方面的难题,请发邮件至我们邮箱statatraining@163.com,届时会有俱乐部高级会员为您排忧解难!
在上一篇推文中,小编给大家简单地介绍了scrapy框架,可能有不少人看得一头雾水,而且心里还想着,为什么要使用scrapy,用requests不行吗?这里给大家这样的一张对比表:
所以,对于爬虫,requests + 任何解析器都是非常好的组合。这样用的优点是我们可以灵活的写我们自己的代码,不必拘泥于固定模式。但是不同于这些模块,scrapy则是一个框架,因此它具有更为人性化的优点:
第一、里面的许多条条框框都已经帮你写好了,我们只需要根据自己的需求定制自己要实现的功能就好了,大大减少了工作量。那么具体哪些是需要我们做的呢?看看下面这个图就明白了。
第二、scrapy基于事件的机制,利用twisted的设计实现了非阻塞的异步操作。这相比于传统的阻塞式请求,极大的提高了CPU的使用率,以及爬取效率。
第三、看似scrapy使用时比requests要繁琐很多,后者只需要调用一下requests类,然后配置一下成员变量就可以使用,但获取到html后其他的事情就都得你自己处理。而scrapy在配置好后就可以很顺畅的跑起来,还会自动处理很多东西,而且往往效率比自己编写的要高。
说了这么多,精简成一句话就是:小爬虫,建议使用requests;大爬虫,建议使用scrapy。
实战演练
接下来就继续以第一篇推文中的“希中网”作为例子进行演示如何用scrapy抓取多级页面。
首先,根据第一篇推文的基本操作,以https://www.cgw.gr为初始网页,创建Demo文件夹,这一次我们抓取的是每个新闻的信息,所以我们配置items.py中的类DemoItem:
其次,我们开始编写demo.py,在初始demo.py文件中,我们可以看到:
可以发现start_urls里的网址多了最后一个“/”,记得将其删除。
在本次demo.py文件中,我们会编写多个函数,分别对应在不同页面我们会进行的操作。在parse函数中,我们主要获取网页导航中的“希腊新闻”对应的链接。
class DemoSpider(scrapy.Spider):
name= 'demo'
allowed_domains = ['www.cgw.gr']
start_urls = ['https://www.cgw.gr']
def parse(self, response):
html = response.text
news =self.start_urls[0]+re.findall(r'<li><ahref="(.*?)">希腊新闻</a></li>',html)[0]
yield scrapy.Request(url=news,meta={'news_1':news},callback=self.allpage)
news变量即为“希腊新闻”对应的链接,然后yield生成请求对象,其中,url为需要下一步进行处理的url,meta可以以字典的形式将指定信息传递给下一个函数,callback为指定下一个处理该reponse的函数为”allpage”。
def allpage(self,response):
news_1 =response.meta['news_1'].replace(u'.htm',u'')
html = response.text
num = re.findall(r'<label>当前页:1/(\d+)',html)
for i in range(1, int(num[0]) + 1):
yield scrapy.Request(news_1 +'_page' + str(i) + '.htm', callback=self.newslist
在allpage函数中,我们构造了560多页的新闻列表的链接,并将其分别一个一个传输给下一个newlist函数。
def newslist(self, response):
html = response.text
items = [] #定义一个空列表
urllist=re.findall(r'<li><span>(.*?)</span><ahref="(.*?)">(.*?)</a></li>',html)
for time,url,title in urllist:
item = DemoItem()
item['url'] = self.start_urls[0] +url
item['time'] = time
item['title'] = title
items.append(item)
print(items)
for item in items:
yield scrapy.Request(url=item['url'],meta={"item": item}, callback=self.gethtmlinfo)
在newlist函数中,我们将新闻的发布时间、链接、题目先保存到item中,并将这个item通过meta参数继续传输给下一个函数中,并将每个新闻的链接也传递给下一个函数。
def gethtmlinfo(self,response):
item = response.meta['item'] #继承上一个函数中的item
html = response.text
if re.findall(r'来源:</strong>(.*?)</li>',str(html)) != []:
item['source'] = re.findall(r'来源:</strong>(.*?)</li>',html)[0]
else:
item['source'] = 'None' #同理其他信息
yield item
这样,我们就可以通过构造这四个函数对所有的新闻进行抓取。
如何保存这些数据呢,在这里我还是选择mysql,因此在pipelines.py文件中,可以参照《如何将Python爬取到的数据保存到MySQL中》中的代码进行编写,下面只写出关键性语句。
import pymysql
#经过demo.py中四个函数的处理后,数据被传输到pipelines.py的DemoPipeline类中
class DemoPipeline(object):
def process_item(self, item,spider):
table = 'XZW'
keys = ', '.join(item.keys())
values = ', '.join(['%s'] * len(item))
cursor = db.cursor()
sql = 'replace INTO {table} ({keys})VALUES ({values})'.format(table=table, keys=keys, values=values)
try:
if cursor.execute(sql,tuple(item.values())):
db.commit()
except BaseException as e:
print(e)
db.rollback()
return item
如果忘记了,可以回顾《如何将Python爬取到的数据保存到MySQL中》。
最后别忘了到settings.py中,将67-69行前的备注符号“#”删去。
然后在终端中输入:scrapy crawl demo,程序就飞快的运转起来了。
我们去MySQL数据库查看一下,信息也很好地保存下来了:
完整程序如下所示:
Demo.py
# -*- coding: utf-8 -*-
import scrapyimport refrom Demo.items
import DemoItemclass DemoSpider(scrapy.Spider):
name = 'demo'
allowed_domains = ['www.cgw.gr']
start_urls = ['https://www.cgw.gr']
def parse(self, response):
html = response.text
news =self.start_urls[0]+re.findall(r'<li><ahref="(.*?)">希腊新闻</a></li>',html)[0]
yieldscrapy.Request(url=news,meta={'news_1':news},callback=self.allpage)
def allpage(self, response):
news_1 = response.meta['news_1'].replace(u'.htm', u'')
html = response.text
num = re.findall(r'<label>当前页:1/(\d+)',html)
for i in range(1, int(num[0]) +1):
yield scrapy.Request(news_1 + '_page' + str(i) + '.htm',callback=self.newslist)
def newslist(self, response):
html = response.text
items = []
urllist=re.findall(r'<li><span>(.*?)</span><ahref="(.*?)">(.*?)</a></li>',html)
for time,url,title in urllist:
item = DemoItem()
item['url'] = self.start_urls[0] + url
item['time'] = time
item['title'] = title
items.append(item)
#print(items)
for item in items:
yield scrapy.Request(url=item['url'], meta={"item": item},callback=self.gethtmlinfo)
def gethtmlinfo(self,response):
item = response.meta['item']
html = response.text
if re.findall(r'来源:</strong>(.*?)</li>',str(html)) != []:
item['source'] = re.findall(r'来源:</strong>(.*?)</li>',html)[0]
else:
item['source'] = 'None'
if re.findall(r'作者:</strong>(.*?)</li>',html) != []:
item['author'] = re.findall(r'作者:</strong>(.*?)</li>',html)[0]
else:
item['author'] = 'None'
if re.findall(r'<strong>浏览数:</strong>(\d+)</ul>',html) != []:
item['views'] = re.findall(r'<strong>浏览数:</strong>(\d+)</ul>',html)[0]
else:
item['views'] = 'None'
html = re.sub(r'<divid="Description">|<divclass="clear"></div></div>', '❤', html)
zhengwen = re.split(r'❤', html)[1]
item['text'] = re.sub(r'<.*?>|\s| ', '', zhengwen)
if re.findall(r'src="(.*?)"', zhengwen) == []:
item['picture'] = 'None'
elif re.findall(r'src="(.*?)"',zhengwen)[0].startswith('/upfile'):
item['picture'] = ','.join(['http://www.cgw.gr'+ x for x in re.findall(r'src="(.*?)"', zhengwen)])
else:
item['picture'] = ','.join(re.findall(r'src="(.*?)"',zhengwen))
yield item
pipelines.py
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to theITEM_PIPELINES setting
# See:https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import pymysqlclass DemoPipeline(object):
def process_item(self, item, spider):
db = pymysql.connect(host='localhost', user='root',password='xzhxll111', port=3306, db='spiders')
cursor = db.cursor()
sql = 'CREATE TABLE IF NOT EXISTS XZW (url VARCHAR(255) NOT NULL,titleVARCHAR(255) NOT NULL, ' \
'source VARCHAR(255) NOT NULL,author VARCHAR(255) NOT NULL,' \
'time VARCHAR(255) NOT NULL,viewsVARCHAR(255) NOT NULL,' \
'text mediumtext NOT NULL,picturemediumtext NOT NULL,PRIMARY KEY (title))'
cursor.execute(sql)
table = 'XZW'
keys = ', '.join(item.keys())
values = ', '.join(['%s'] * len(item))
cursor = db.cursor()
sql = 'replace INTO {table} ({keys}) VALUES({values})'.format(table=table, keys=keys, values=values)
try:
if cursor.execute(sql, tuple(item.values())):
print('Successful')
db.commit()
except BaseException as e:
print("错误在这里>>>>", e,"<<<<<<错误在这里")
print('Failed')
db.rollback()
return item
#结束后做的操作,在这里我们要关闭文件
def close_spider(self, spider):
print('结束')
对爬虫俱乐部的推文累计打赏超过1000元我们即可给您开具发票,发票类别为“咨询费”,目前第五批发票已经寄到各位读者的手中。用心做事,只为做您更贴心的小爬虫!
往期推文推荐:
关于我们
微信公众号“爬虫俱乐部”分享实用的stata命令,欢迎转载、打赏。爬虫俱乐部是由李春涛教授领导下的研究生及本科生组成的大数据分析和数据挖掘团队。
此外,欢迎大家踊跃投稿,介绍一些关于stata的数据处理和分析技巧。
投稿邮箱:statatraining@163.com
投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到关于stata分析数据的问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。