Python网络爬虫基础
- 一、爬虫框架
- 1. 什么是框架?
- 2. 初期如何学习框架?
- 二、scrapy 入门
- 1. 网络爬虫
- 请求数据
- 解析数据
- 保存数据
- 2. scrapy安装
- 安装方式
- 全局命令
- 项目命令
- 案例 - scrapy 下厨房网爬取
- settings.py
- spiders
- blood.py
- 案例 - scrapy爬取哔哩哔哩网
- settings.py
- items.py
- pipelines.py
- iders
- libi.py - 基于终端指令的持久化存储(简单)不推荐
- libi,py - 基于管道的持久化存储方式(通用)
- 案例 - scrapy数据保存到数据库
- settings.py
- items.py
- pipelines.py
- spiders
- libi.py
- 案例 - scrapy爬取二进制数据
- settings.py
- items.py
- pipelines.py
- spiders
- img.py
一、爬虫框架
1. 什么是框架?
所谓的框架,其实说白了就是一个【项目的半成品】,该项目的半成品需要被集成了各种功能且具有较强的通用性。
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。
2. 初期如何学习框架?
只需要学习框架集成好的各种功能的用法即可!前期切勿钻研框架的源码!
二、scrapy 入门
1. 网络爬虫
网络爬虫是指在互联网上自动爬取网站内容信息的程序,也被称作网络蜘蛛或网络机器人。大型的爬虫程序被广泛应用于搜索引擎、数据挖掘等领域,个人用户或企业也可以利用爬虫收集对自身有价值的数据。
一个网络爬虫程序的基本执行流程可以总结三个过程:请求数据
, 解析数据
, 保存数据
请求数据
请求的数据除了普通的HTML之外,还有 json 数据、字符串数据、图片、视频、音频等。
解析数据
当一个数据下载完成后,对数据中的内容进行分析,并提取出需要的数据,提取到的数据可以以多种形式保存起来,数据的格式有非常多种,常见的有csv、json、pickle等
保存数据
最后将数据以某种格式(CSV、JSON)写入文件中,或存储到数据库(MySQL、MongoDB)中。同时保存为一种或者多种。
-
通常,我们想要获取的数据并不只在一个页面中,而是分布在多个页面中,这些页面彼此联系,一个页面中可能包含一个或多个到其他页面的链接,提取完当前页面中的数据后,还要把页面中的某些链接也提取出来,然后对链接页面进行爬取(循环1-3步骤)。
-
设计爬虫程序时,还要考虑防止重复爬取相同页面(URL去重)、网页搜索策略(深度优先或广度优先等)、爬虫访问边界限定等一系列问题。
-
从头开发一个爬虫程序是一项烦琐的工作,为了避免因制造轮子而消耗大量时间,在实际应用中我们可以选择使用一些优秀的爬虫框架,使用框架可以降低开发成本,提高程序质量,让我们能够专注于业务逻辑(爬取有价值的数据)。接下来,就带你学习目前非常流行的开源爬虫框架
Scrapy
。
2. 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 这条命令:
Scrapy 2.4.0 - no active projectUsage:scrapy <command> [options] [args]Available commands:bench Run quick benchmark testfetch Fetch a URL using the Scrapy downloadergenspider Generate new spider using pre-defined templatesrunspider Run a self-contained spider (without creating a project)settings Get settings valuesshell Interactive scraping consolestartproject Create new projectversion Print Scrapy versionview Open URL in browser, as seen by Scrapy[ more ] More commands available when run from project directoryUse "scrapy <command> -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 projectUsage: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 directoryUse "scrapy <command> -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
运行爬虫。注意该命令运行时所在的目录
案例 - scrapy 下厨房网爬取
settings.py
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 = FalseREQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
spiders
blood.py
import scrapyclass 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 参数就表示响应对象# 如何实现数据解析 : Xpathli_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 ,否则使用 extractprint(title)
案例 - scrapy爬取哔哩哔哩网
settings.py
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 = FalseREQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
items.py
import scrapyclass BiliproItem(scrapy.Item):title = scrapy.Field()author = scrapy.Field()
pipelines.py
# 存储 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()
iders
libi.py - 基于终端指令的持久化存储(简单)不推荐
import scrapyclass 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
libi,py - 基于管道的持久化存储方式(通用)
import scrapyfrom ..items import BiliproItemclass 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对象进行数据持久化存储
案例 - scrapy数据保存到数据库
- 如何将数据存储到数据库
- 注意:一个管道类负责将数据存储到一个具体的载体中。如果想要将爬取到的数据存储到多个不同的载体/数据库中,则需要定义多个管道类。
- 思考:
- 在有多个管道类的前提下,爬虫文件提交的item会同时给每一个管道类还是单独的管道类?
- 爬虫文件只会将item提交给优先级最高的那一个管道类。优先级最高的管道类的process_item中需要写return item操作,该操作就是表示将item对象传递给下一个管道类,下一个管道类获取了item对象,才可以将数据存储成功!
- 在有多个管道类的前提下,爬虫文件提交的item会同时给每一个管道类还是单独的管道类?
settings.py
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 = FalseITEM_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"
items.py
import scrapyclass BiliproItem(scrapy.Item):title = scrapy.Field()author = scrapy.Field()
pipelines.py
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 itemdef close_spider(self, spider):self.conn.close()self.cursor.close()''' 第三个管道类:存储 Redis 数据库 '''
class RedisPipeLine:conn = Nonedef 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 itemdef 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
spiders
libi.py
import scrapyfrom ..items import BiliproItemclass 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 对象的数据存储到指定的平台或者载体中
- 在配置文件中开启管道的机制
案例 - scrapy爬取二进制数据
使用一个专有的管道类ImagesPipeline
http://pic.netbian.com/4kmeinv/
首先安装插件 : pip install PIL / pip install Pillow
- 具体的编码流程:
- 1.在爬虫文件中进行图片/视频的链接提取
- 2.将提取到的链接封装到items对象中,提交给管道
- 3.在管道文件中自定义一个父类为ImagesPipeline的管道类,且重写三个方法即可:
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对象给下一个管道类
settings.py
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"
items.py
import scrapyclass ImgproItem(scrapy.Item):img_src = scrapy.Field()
pipelines.py
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.
spiders
img.py
import scrapy
from ..items import ImgproItemclass 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# 特殊的管道类:主要是对二进制的数据进行持久化存储