网上有很多关于Scrapy的安装方式,这里不再说了。
新建一个爬虫项目
scrapy startproject quotestutorial
利用上面的命令新建一个scrapy项目,项目名是quotestutorial,因为我爬取的网址是http://quotes.toscrape.com
,这是一个格言的网址。现在就可以在当前目录看到一个名为quotestutorial的文件夹。
新建一个爬虫
scrapy genspider quotes quotes.toscrape.com
其中quotes
是爬虫的名字,quotes.toscrape.com
是要爬取的网站的域名,注意,一定是域名,不可以带协议比如http://
。
用编辑器打开当前项目,看到目录结构如下所示。
spiders文件夹
spiders文件夹存放你构建的Spider类,比如我现在的quotes.py中,有如下代码。
# -*- coding: utf-8 -*-
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com/']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
pass
这些代码是在你执行scrapy genspider quotes quotes.toscrape.com
的时候自动生成的,allowed_domains
列举了你爬的网站域名,start_url
列举了你想要爬取的页面的起始页。
下面的函数 parse
是一个回调函数,就是当爬取页面成功时,该做些生么,由你自己定义,其中的参数response
中就是一些处理网页的API。
setting.py
这里定义一些配置参数。
items.py
items.py中的初识内容如下
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class QuotestutorialItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
从描述中可以看出这里是定义爬取模型的,也就是说我们想要把我们爬取的内容,变成结构化的数据,类似于Java中bean,可以在这里定义封装格式。定义方式就是
name = scrapy.Field()
age = scrapy.Field()
...
我想爬取格言网站的信息,提取出content
、author
、tags
,三个信息。于是定义Item为。
class QuotestutorialItem(scrapy.Item):
content = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
pipelines.py
这里的初始内容如下
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from scrapy.exceptions import DropItem
class QuotestutorialPipeline(object):
def process_item(self, item, spider):
pass
pipeline从名字上可是管道的意思,其实就是在生成item之后,如何处理这些item。这里可以定义自己的逻辑。
process_item
函数就是逻辑代码定义的地方,item就是上面items.py中定义的Item。
除了process_item
方法外,其实还有两个方法,可以对他们进行重写,一个是
@classmethod
def from_crawler(cls, crawler):
...
return cls(
...
)
这里返回一个当前类的对象,也就是当前pipeline的对象,从crawler中可以获取setting.py中的配置参数
crawler.settings.get('[参数名]')
另一个是
def open_spider(self, spider):
pass
这里可以写爬虫初始化结束,还未开始爬取的时候的逻辑代码。
开始写爬虫
写Spider
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com/']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
content = quote.css('.text ::text').extract_first()
author = quote.css('.author ::text').extract_first()
tags = quote.css('.tags .tag::text').extract()
item = QuotestutorialItem()
item['content'] = content
item['author'] = author
item['tags'] = tags
yield item
# /page/2/
next = response.css('.pager .next a ::attr(href)').extract_first()
if next:
url = response.urljoin(next)
yield scrapy.Request(url=url, callback=self.parse, dont_filter=True)
上面代码不难理解,有几点需要说明的。
- response.css(…)
response
的css
函数可以通过选择器获取页面的结点内容, 其中的::text
是固定写法,表示我要获取结点中的text,extract_first
是只取第一个结点,因为只有一个。extract
函数是取所有节点,tag有多个。 - item是一个dict
上面的代码不难看出item是一个dict类型,这里的item就是我们在items.py中定义的。 - yield item
通过yield方式可以返回一个遍历器,其实这里的遍历器在底层应该是将item传到我们定义的pipeline中做处理了,当然pipeline需要开启才行,后面会说到,如果pipline没有开启,则默认会打印到控制台。 - 翻页
这里的最后一句代码yield scrapy.Request(url=url, callback=self.parse, dont_filter=True)
, next是翻页按钮的href,这里是递归的翻页,直到最后一页为止。
开始运行
到目前为止,其实就可以运行了。在命令行输入
scrapy crawl quotes
即可运行名为quotes的爬虫。
如果没有出现你爬取的信息,有以下几种可能
- robot.txt
这是网站的一个爬虫规则,如果网站中包含这个robot.txt,则网站很有可能与反爬虫措施。如果你在控制台发现如下类似的语句,
<GET http://rd.139site.com/?e=dns&t=http/robots.txt> from <GET http://http/robots.txt>
则就可能是这种情况。
解决办法是,在setting.py中将ROBOTSTXT_OBEY = True
修改为ROBOTSTXT_OBEY = False
,我们不遵守机器人条约。
- Header
在setting.py中找到DEFAULT_REQUEST_HEADERS
,在里面添加User-Agent
, 这个值和浏览器有关,可以在浏览器开发者模式的Network中随便找一个请求,找到其中的Headers选项卡,找到User-Agent,比如我的是
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
可以将爬取结果保存到文件
只需要修改运行命令
scrapy crawl quteos -o qutoes.csv
在爬取的过程中即可将爬取的数据写入项目根目录的qutoes.csv中,格式是scrapy根据我们的后缀名弄好的,如果后缀名不是csv,是txt或者json,格式都是不一样的。很方便。
写pipeline
我们定义两个pipeline
from scrapy.exceptions import DropItem
class TextLengthPipeline(object):
def __init__(self):
self.limit = 50
def process_item(self, item, spider):
if item['content']:
if len(item['content']) > self.limit:
item['content'] = item['content'][0:self.limit] + '...'
else:
return DropItem('Missing Content...')
return item
class RedisPipeline(object):
def __init__(self, uri, username, password):
self.uri = uri
self.username = username
self.password = password
def process_item(self, item, spider):
print('写入到Redis喽...')
@classmethod
def from_crawler(cls, crawler):
return cls(
uri=crawler.settings.get('URI'),
username=crawler.settings.get('USERNAME'),
password=crawler.settings.get('PASSWORD')
)
def open_spider(self, spider):
print('爬虫被初始化...')
print('uri = ', self.uri)
print('username = ', self.username)
print('password = ', self.password)
可以读懂代码,第一个pipeline我想把格言的内容,大于50字符的部分,用...
代替,第二个pipeline我想将爬取的数据写入Redis数据库,因为代码较长,所以没有把实现代码列出来。
注意
写完pipeline之后,要在setting.py中打开pipeline
ITEM_PIPELINES = {
'quotestutorial.pipelines.TextLengthPipeline': 300,
'quotestutorial.pipelines.RedisPipeline': 400
}
其中的数字,300,400 指的是先后顺序,数字越小,越先执行。