所谓的框架,其实说白了就是一个【项目的半成品】,该项目的半成品需要被集成了各种功能且具有较强的通用性。
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。
只需要学习框架集成好的各种功能的用法即可!前期切勿钻研框架的源码!
网络爬虫是指在互联网上自动爬取网站内容信息的程序,也被称作网络蜘蛛或网络机器人。大型的爬虫程序被广泛应用于搜索引擎、数据挖掘等领域,个人用户或企业也可以利用爬虫收集对自身有价值的数据。
一个网络爬虫程序的基本执行流程可以总结三个过程:请求数据
, 解析数据
, 保存数据
请求的数据除了普通的HTML之外,还有 json 数据、字符串数据、图片、视频、音频等。
当一个数据下载完成后,对数据中的内容进行分析,并提取出需要的数据,提取到的数据可以以多种形式保存起来,数据的格式有非常多种,常见的有csv、json、pickle等
最后将数据以某种格式(CSV、JSON)写入文件中,或存储到数据库(MySQL、MongoDB)中。同时保存为一种或者多种。
通常,我们想要获取的数据并不只在一个页面中,而是分布在多个页面中,这些页面彼此联系,一个页面中可能包含一个或多个到其他页面的链接,提取完当前页面中的数据后,还要把页面中的某些链接也提取出来,然后对链接页面进行爬取(循环1-3步骤)。
设计爬虫程序时,还要考虑防止重复爬取相同页面(URL去重)、网页搜索策略(深度优先或广度优先等)、爬虫访问边界限定等一系列问题。
从头开发一个爬虫程序是一项烦琐的工作,为了避免因制造轮子而消耗大量时间,在实际应用中我们可以选择使用一些优秀的爬虫框架,使用框架可以降低开发成本,提高程序质量,让我们能够专注于业务逻辑(爬取有价值的数据)。接下来,就带你学习目前非常流行的开源爬虫框架Scrapy
。
scrapy官网: https://scrapy.org/
scrapy中文文档:https://www.osgeo.cn/scrapy/intro/overview.html
在任意操作系统下,可以使用pip安装Scrapy,例如:
Linux/mac系统:
- pip install scrapy(任意目录下)
Windows系统:可以直接pip install scrapy安装,如果安装出错可以采用如下方式:
- 1.pip install wheel (任意目录下)
- 2.下载 twisted 文件,下载网址如下: http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
- 3.终端进入下载目录,执行 pip install Twisted-17.1.0-cp35-cp35m-win_amd64.whl
注意:如果该步骤安装出错,则换一个版本的 whl 文件即可- 4.pip install pywin32 (任意目录下)
如果安装好后,在终端中录入 scrapy 指令按下回车,如果没有提示找到该指令,则表示安装成功
安装完成后我们需要测试安装是否成功,通过如下步骤确认:
Scrapy 2.4.0 - no active project
Usage:
scrapy <command> [options] [args]
Available commands:
bench Run quick benchmark test
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
[ more ] More commands available when run from project directory
Use "scrapy -h" to see more info about a command
scrapy bench
测试连通性,如果出现以下情况表示安装成功:
通过了以上两项检测,说明Scrapy安装成功了。如上所示,我们安装的是当前最新版本2.4.0。
注意:
成功安装后,在CMD下运行scrapy出现上图不算真正成功,检测真正是否成功使用scrapy bench
测试,如果没有提示错误,就代表成功安装。
具体Scrapy安装流程参考:http://doc.scrapy.org/en/latest/intro/install.html##intro-install-platform-notes
里面有各个平台的安装方法
Scrapy 2.4.0 - no active project
Usage:
scrapy <command> [options] [args]
Available commands:
bench Run quick benchmark test
# 测试电脑性能
fetch Fetch a URL using the Scrapy downloader
# 将源代码下载下来并显示出来
genspider Generate new spider using pre-defined templates
# 创建一个新的 spider 文件
runspider Run a self-contained spider (without creating a project)
# 这个和通过crawl启动爬虫不同,scrapy runspider 爬虫文件名称
settings Get settings values
# 获取当前的配置信息
shell Interactive scraping console
# 进入 scrapy 的交互模式
startproject Create new project
# 创建爬虫项目
version Print Scrapy version
# 显示scrapy框架的版本
view Open URL in browser, as seen by Scrapy
# 将网页document内容下载下来,并且在浏览器显示出来
[ more ] More commands available when run from project directory
Use "scrapy -h" to see more info about a command
scrapy startproject projectname项目名称
## 创建一个项目
firstBlood # 项目所在文件夹, 建议用pycharm打开该文件夹
├── firstBlood # 项目跟目录
│ ├── __init__.py
│ ├── items.py # 封装数据的格式
│ ├── middlewares.py # 所有中间件
│ ├── pipelines.py # 所有的管道
│ ├── settings.py # 爬虫配置信息
│ └── spiders # 爬虫文件夹, 稍后里面会写入爬虫代码
│ └── __init__.py
└── scrapy.cfg # scrapy项目配置信息,不要删它,别动它,善待它.
cd project_name(进入项目目录)
scrapy genspider 爬虫文件的名称 (自定义一个名字即可) 起始url (随便写一个网址即可)
创建好爬虫项目以后,还需要创建爬虫。
scrapy crawl spidername
运行爬虫。注意该命令运行时所在的目录
BOT_NAME = "First"
SPIDER_MODULES = ["First.spiders"]
NEWSPIDER_MODULE = "First.spiders"
# 指定输出的日志类型
LOG_LEVEL = 'ERROR'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36"
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
import scrapy
class BloodSpider(scrapy.Spider):
# 爬虫文件的唯一标识
name = "blood"
# 允许的域名
allowed_domains = ["www.baidu.com"]
# 起始的 url 列表(重要):列表内部的 url 都会被框架进行异步的请求发送
start_urls = ["https://www.xiachufang.com/category/40076/"]
# 数据解析 : parse 调用的次数取决于 start_urls 列表元素的个数
def parse(self, response): # response 参数就表示响应对象
# 如何实现数据解析 : Xpath
li_list = response.xpath('/html/body/div[4]/div/div/div[1]/div[1]/div/div[2]/div[2]/ul/li')
for li in li_list:
# xpath 最终会返回的是 Selector 对象,想要的解析的数据是存储在该对象的 data 属性中(extract可以实现该功能)
# title = li.xpath('./div/div/p[1]/a/text()')[0].extract() # 一般不用
# extract_first 可以将 xpath 返回类别中的第一个 Selector 对象中的 data 属性值获取
# title = li.xpath('./div/div/p[1]/a/text()').extract_first()
# extract 可以将 xpath 返回列表中的每一个 Selector 对象中的 data 属性值获取
title = li.xpath('./div/div/p[1]/a/text()').extract()
# 如果 xpath 返回的列表元素只有一个则使用 extract_first ,否则使用 extract
print(title)
BOT_NAME = "biliPro"
SPIDER_MODULES = ["biliPro.spiders"]
NEWSPIDER_MODULE = "biliPro.spiders"
# 指定输出的日志类型
LOG_LEVEL = 'ERROR'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36"
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
import scrapy
class BiliproItem(scrapy.Item):
title = scrapy.Field()
author = scrapy.Field()
# 存储 txt 文件
class BiliproPipeline:# 一个管道类只负责将数据存储到一个载体中
fp = None
# 全程只会被调用一次
def open_spider(self, spider):
print('i am open_spider()')
self.fp = open('bili.txt', 'w')
# process_item 函数就是用来接受爬虫文件提交过来的item对象,且可以将item对象中的数据存储到任何载体中
def process_item(self, item, spider): # 参数 item 就是管道接收到item对象
title = item['title']
author = item['author']
# 数据存储到文件里
self.fp.write(author + ':' + title + '\n')
return item
# process_item 函数调用的次数取决于爬虫文件给管道提交的 item 的次数
def close_spider(self, spider):
print('i am close_spider()')
# 该函数只会在爬虫结束前被调用一次
self.fp.close()
import scrapy
class LibiSpider(scrapy.Spider):
name = "libi"
allowed_domains = ["www.xxx.com"]
start_urls = [
"https://search.bilibili.com/all?keyword=%E5%AE%8F%E8%A7%82%E7%BB%8F%E6%B5%8E&from_source=webtop_search&spm_id_from=333.1007&search_source=5"]
# 基于终端指令的持久化存储(简单):只可以将 parse 方法的返回值存储写入到指定后缀的文本文件中
def parse(self, response):
div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div')
all_data = []
for div in div_list:
title = div.xpath('./div/div[2]/div/div/a/h3/text()').extract()
title = ''.join(title)
author = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
dic = {
'title': title,
'author': author,
}
all_data.append(dic)
return all_data # all_data里面就存储了爬取到的数据
# 指令 scrapy crawl bili -o bili.csv
import scrapy
from ..items import BiliproItem
class LibiSpider(scrapy.Spider):
name = "libi"
allowed_domains = ["www.xxx.com"]
start_urls = [
"https://search.bilibili.com/all?keyword=%E5%AE%8F%E8%A7%82%E7%BB%8F%E6%B5%8E&from_source=webtop_search&spm_id_from=333.1007&search_source=5"]
# 基于管道的持久化存储方式(通用)
def parse(self, response):
div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div')
all_data = []
for div in div_list:
title = div.xpath('./div/div[2]/div/div/a/h3/text()').extract()
title = ''.join(title)
author = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
# 创建一个 item 类型的对象
item = BiliproItem(title=title, author=author)
yield item
# 编码流程:1.解析数据 2.创建一个 item 类的对象(存储解析出来的数据)3.将解析出来的数据存储到该 item 类型的对象中 4.将item对象提交给管道
# 爬虫文件:libi.py 进行请求发送和数据解析
# item文件: items.py 定义n个变量
# 管道文件: pipelines.py 接收item对象进行数据持久化存储
BOT_NAME = "biliPro"
SPIDER_MODULES = ["biliPro.spiders"]
NEWSPIDER_MODULE = "biliPro.spiders"
# 指定输出的日志类型
LOG_LEVEL = 'ERROR'
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36"
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
# value 值表示的数字代表了管道类的优先级,数字越小表示优先级越高
"biliPro.pipelines.BiliproPipeline": 300,
"biliPro.pipelines.MysqlPipeline": 301,
"biliPro.pipelines.RedisPipeLine": 302,
"biliPro.pipelines.MongoPipeline": 303,
}
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
import scrapy
class BiliproItem(scrapy.Item):
title = scrapy.Field()
author = scrapy.Field()
import pymysql # pip install pymysql
''' 第一个管道类:存储 txt 文件 '''
class BiliproPipeline: # 一个管道类只负责将数据存储到一个载体中
fp = None
# 全程只会被调用一次
def open_spider(self, spider):
print('i am open_spider()')
self.fp = open('bili.txt', 'w')
# process_item 函数就是用来接受爬虫文件提交过来的item对象,且可以将item对象中的数据存储到任何载体中
def process_item(self, item, spider): # 参数 item 就是管道接收到item对象
title = item['title']
author = item['author']
# 数据存储到文件里
self.fp.write(author + ':' + title + '\n')
return item
# process_item 函数调用的次数取决于爬虫文件给管道提交的 item 的次数
def close_spider(self, spider):
print('i am close_spider()')
# 该函数只会在爬虫结束前被调用一次
self.fp.close()
''' 第二个管道类:存储到 mysql 数据库 '''
class MysqlPipeline:
conn = None # 链接对象
cursor = None # 游标对象
def open_spider(self, spider):
# 链接数据库的操作只需要被执行一次
self.conn = pymysql.Connect(
host='127.0.0.1', # mysql 数据库服务器的ip地址
port=3306, # 端口号
user='root', # 用户名
password='root', # 密码
db='spider', # 数据仓库名称
)
# 创建一个游标对象(用来使用python程序执行sql语句)
self.cursor = self.conn.cursor()
def process_item(self, item, spider): # 参数 item 就是管道接收到item对象,由上级优先级高的管道类传递过来
title = item['title']
author = item['author']
# 使用游标对象 cursor 执行 sql语句
sql = 'insert into bili values ("%s","%s")' % (title, author)
self.cursor.execute(sql)
# 提交事物
self.conn.commit()
return item
def close_spider(self, spider):
self.conn.close()
self.cursor.close()
''' 第三个管道类:存储 Redis 数据库 '''
class RedisPipeLine:
conn = None
def open_spider(self, spider):
# 创建 redis 的链接对象
self.conn = Redis(
host='127.0.0.1',
port=3308
)
def process_item(self, item, spider): # 参数 item 就是管道接收到item对象,由上级优先级高的管道类传递过来
# item 本身就是一个字典
self.conn.lpush('libi', item)
return item
def close_spider(self, spider):
pass
''' 第四个管道类:存储 MongoDB 数据库 '''
import pymongo
class MongoPipeline:
conn = None # 链接对象
db_sanqi = None # 数据仓库
def open_spider(self, spider):
self.conn = pymongo.MongoClient(
host='127.0.0.1',
port=27017
)
self.db_sanqi = self.conn['sanqi']
def process_item(self, item, spider):
self.db_sanqi['xiaoshuo'].insert_one({'title': item['title']})
print('插入成功!')
return item
import scrapy
from ..items import BiliproItem
class LibiSpider(scrapy.Spider):
name = "libi"
allowed_domains = ["www.xxx.com"]
start_urls = [
"https://search.bilibili.com/all?keyword=%E5%AE%8F%E8%A7%82%E7%BB%8F%E6%B5%8E&from_source=webtop_search&spm_id_from=333.1007&search_source=5"]
# 基于管道的持久化存储方式(通用)
def parse(self, response):
div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[3]/div/div')
all_data = []
for div in div_list:
title = div.xpath('./div/div[2]/div/div/a/h3/text()').extract()
title = ''.join(title)
author = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
# 创建一个 item 类型的对象
item = BiliproItem(title=title, author=author)
yield item # item会提交给那个管道类?一定是提交给优先级最高的管道类!!
# 编码流程:1.解析数据 2.创建一个 item 类的对象(存储解析出来的数据)3.将解析出来的数据存储到该 item 类型的对象中 4.将item对象提交给管道
# 爬虫文件:libi.py 进行请求发送和数据解析
# item文件: items.py 定义n个变量
# 管道文件: pipelines.py 接收item对象进行数据持久化存储
- 在爬虫文件中进行数据爬取和数据解析
- 在 items.py 文件中进行相关变量的定义(变量的个数取决于爬虫文件中解析字段的个数)
- 在爬虫文件中将解析到的数据存储到item类型的对象中
- 将 item 类型的对象提交给管道
- 管道的 process_item 函数中接收item对象,且将 item 对象的数据存储到指定的平台或者载体中
- 在配置文件中开启管道的机制
使用一个专有的管道类ImagesPipeline
http://pic.netbian.com/4kmeinv/
首先安装插件 : pip install PIL / pip install Pillow
def get_media_requests(self, item, info):接收爬虫文件提交过来的item对象,然后对图片地址发起网路请求,返回图片的二进制数据
def file_path(self, request, response=None, info=None, *, item=None):指定保存图片的名称
def item_completed(self, results, item, info):返回item对象给下一个管道类
BOT_NAME = "imgPro"
SPIDER_MODULES = ["imgPro.spiders"]
NEWSPIDER_MODULE = "imgPro.spiders"
# 指定输出的日志类型
LOG_LEVEL = 'ERROR'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
IMAGES_STORE = 'girlsLib'
ITEM_PIPELINES = {
"imgPro.pipelines.BytesPipeLine": 300,
}
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
import scrapy
class ImgproItem(scrapy.Item):
img_src = scrapy.Field()
import scrapy
from scrapy.pipelines.images import ImagesPipeline
# 普通的管道类:将文本数据持久化
class ImgproPipeline:
def process_item(self, item, spider):
return item
# 特殊的管道类:将二进制数据持久化
# 自定义了一个管道类,该类的父类为 ImagesPipeline
class BytesPipeLine(ImagesPipeline):
# 重写三个父类的方法来完成图片二进制数据的请求和持久化存储
# 可以根据图片地址,对其进行请求,获取图片数据
# 接收爬虫文件提交过来的item对象,并且可以对相关的多媒体资源进行网络请求
def get_media_requests(self, item, info):
# 提取图片地址或者视频地址
img_src = item['img_src']
# 可以对 img_src 进行网络请求获取图片数据
yield scrapy.Request(img_src)
def file_path(self, request, response=None, info=None, *, item=None): # 指定保存图片的名称
# 用来将请求到的多媒体数据进行指定路径的存储
# 返回存储文件的名字
img_src = request.url # 图片地址
img_title = img_src.split('/')[-1]
print(img_title, '下载保存成功!')
return img_title
# 如果没有下一个管道类,该方法可以不写
def item_completed(self, results, item, info): # 返回item对象给下一个管道类
return item # 可以将当前的管道类接收到item对象传递给下一个管道类2.
import scrapy
from ..items import ImgproItem
class ImgSpider(scrapy.Spider):
name = "img"
# allowed_domains = ["www.xxx.com"]
start_urls = ["http://pic.netbian.com/4kmeinv/"]
def parse(self, response):
# 解析图片地址
li_list = response.xpath('//*[@id="main"]/div[3]/ul/li')
for li in li_list:
img_src = 'http://pic.netbian.com' + li.xpath('./a/img/@src').extract_first()
# 图片地址封装到item对象中,且将item提交给管道即可
item = ImgproItem(img_src=img_src)
print(item)
yield item
# 特殊的管道类:主要是对二进制的数据进行持久化存储