文章目录
- 系列文章索引
- 一、scrapy简介
- 1、什么是scrapy
- 2、scrapy安装
- 3、scrapy架构组成
- 4、scrapy工作原理
- 二、scrapy基本使用
- 1、创建项目
- 2、创建爬虫文件
- 3、(附)项目组成
- 4、运行爬虫代码
- (1)修改baidu.py
- (2)robots文件
- 5、response的属性和方法
- 6、实战:获取百度的【百度一下】按钮的内容
- 7、实战:获取汽车之家汽车价格表
- 三、使用scrapy shell
- 1、什么是scrapy shell
- 2、安装ipython(非必须)
- 3、使用scrapy shell
- 四、实战:获取当当网商品数据
- 1、初始化项目
- 2、定义item文件
- 3、爬取图片、名字、价格
- 4、管道封装
- (1)熟悉yield
- (2)构造item对象并交给pipeline
- (3)在settings.py中开启pipeline
- (4)编辑pipeline.py
- (5)执行,查看写入的json文件
- 5、多条管道使用
- (1)pipelines.py定义管道类
- (2)settings.py开启管道
- (5)执行,查看写入的json文件与图片
- 6、获取多页数据
- 五、实战:获取电影天堂不同页面的数据
- 1、效果
- 2、核心代码
- 六、实战:使用CrawlSpider获取读书网的数据
- 1、CrawlSpider简介
- 2、创建项目
- 3、定义item
- 4、提取数据
- 5、定义pipeline
- 6、启动
- 7、保存至mysql
- 七、实战:发送post请求
系列文章索引
Python爬虫基础(一):urllib库的使用详解
Python爬虫基础(二):使用xpath与jsonpath解析爬取的数据
Python爬虫基础(三):使用Selenium动态加载网页
Python爬虫基础(四):使用更方便的requests库
Python爬虫基础(五):使用scrapy框架
一、scrapy简介
1、什么是scrapy
Scrapy 是一个为了抓取网页数据、提取结构性数据而编写的应用框架,该框架是封装的,包含 request (异步调度和处理)、下载器(多线程的 Downloader)、解析器(selector)和 twisted(异步处理)等。对于网站的内容爬取,其速度非常快捷。
2、scrapy安装
# 进入到python安装目录的Scripts目录
d:
cd D:\python\Scripts
# 安装 可以使用国内源
pip install scrapy
3、scrapy架构组成
(1)引擎:自动运行,无需关注,会自动组织所有的请求对象,分发给下载器。
(2)下载器:从引擎处获取到请求对象后,请求数据。
(3)spiders:spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。换句话说,Spider就是定义爬取的动作及分析某个网页的地方。
(4)调度器:有自己的调度规则,无需关注。
(5)管道(Item pipeline):最终处理数据的管道,会预留接口供我们处理数据。当Item在Spider中被收集之后,它将会被传递到Item pipeline,一些组件会按照一定的顺序执行对Item的处理。每个item pipeline组件是实现了简单方法的Python类,他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
以下是item pipeline的一些典型应用:
(1)清理HTML数据,(2)验证爬取的数据(检查item包含某些字段),(3)查重(并丢弃),(4)将爬取结果保存到数据库中。
4、scrapy工作原理
spiders->scheduler(调度器)->scrapy engine(引擎)->downloader(下载器)->互联网进行下载
->下载后的数据从引擎到spiders->通过引擎xpath进行数据解析->使用pipeline对数据进行存储
二、scrapy基本使用
1、创建项目
进入到项目目录,打开cmd:
# 创建scrapy_test_001项目,项目名不能以数字、汉字开头
scrapy startproject scrapy_test
2、创建爬虫文件
要在spiders文件夹中去创建爬虫文件
# cd 项目的名字\项目的名字\spiders
cd scrapy_test\scrapy_test\spiders
创建爬虫文件,注意,不需要添加http协议了:
# scrapy genspider 爬虫文件的名字 要爬取网页
scrapy genspider baidu www.baidu.com
此时,在spiders目录中,会生成一个baidu.py:
我们看一下baidu.py的内容:
import scrapy
class BaiduSpider(scrapy.Spider):
# 爬虫的名字 用于运行爬虫的时候 使用的值
name = "baidu"
# 允许访问的域名
allowed_domains = ["www.baidu.com"]
# 起始的url地址 指的是第一次要访问的域名
start_urls = ["https://www.baidu.com"]
# 是执行了start_urls之后 执行的方法 方法中的response 就是返回的那个对象
# 相当于 response = urllib.request.urlopen()
# response = requests.get()
def parse(self, response):
pass
后续我们可以在parse方法中,对response进行处理,这就是最终爬取的结果。
3、(附)项目组成
4、运行爬虫代码
(1)修改baidu.py
在parse方法中,自定义输出:
def parse(self,response):
print('输出正确!')
在spiders目录中,执行以下命令,就可以运行爬虫代码:
# scrapy crawl 爬虫的名字
scrapy crawl baidu
会输出很多内容,但是并没有我们打印的东西。
(2)robots文件
控制台中,打印出了百度的robots协议:
每个网站都会有一个robots君子协议,里面定义了哪些不允许爬取,我们看百度的robots:
https://www.baidu.com/robots.txt
在项目的settings.py文件中,默认是ROBOTSTXT_OBEY=True,表示遵循这个君子协议。
我们只需要将这一行注释掉:
此时我们再执行爬虫代码:
# scrapy crawl 爬虫的名字
scrapy crawl baidu
此时在命令行中,会打印出我们自定义的那句话了。
5、response的属性和方法
response.xpath(xpath_expression):根据XPath表达式选择并提取数据。
response.css(css_expression):根据CSS选择器选择并提取数据。
response.follow(url):根据给定的URL创建新的请求,并通过回调方法继续处理。
response.url:返回当前响应的URL。
response.status:返回当前响应的状态码。
response.headers:返回当前响应的头部信息。
response.body:返回当前响应的原始二进制内容。
response.text:返回当前响应的文本内容。
response.css(‘a::attr(href)’).getall():使用CSS选择器提取所有匹配的元素属性值。
response.xpath(‘//a/@href’).extract():使用XPath表达式提取所有匹配的元素属性值
6、实战:获取百度的【百度一下】按钮的内容
def parse(self, response):
print('=====================')
input = response.xpath('//input[@id="su"]/@value')[0]
print(input.extract()) # 百度一下
print('=====================')
7、实战:获取汽车之家汽车价格表
import scrapy
class CarSpider(scrapy.Spider):
name = 'car'
allowed_domains = ['https://car.autohome.com.cn/price/brand-15.html']
start_urls = ['https://car.autohome.com.cn/price/brand-15.html']
def parse(self, response):
print('=======================')
name_list = response.xpath('//div[@class="main-title"]/a/text()')
price_list = response.xpath('//div[@class="main-lever"]//span/span/text()')
for i in range(len(name_list)):
name = name_list[i].extract()
price = price_list[i].extract()
print(name,price)
print('=======================')
三、使用scrapy shell
1、什么是scrapy shell
scrapy终端,是一个交互终端,供您在未启动spider的情况下尝试及调试您的爬取代码。 其本意是用来测试提取数据的代码,不过您可以将其作为正常的python终端,在上面测试任何的python代码。
该终端是用来测试xPath或css表达式,查看他们的工作方式及从爬取的网页中提取的数据。在编写您的spider时,该终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。一旦熟悉了scrapy终端后,您会发现其在开发和调试spider时发挥的巨大作用。
2、安装ipython(非必须)
# 进入到python安装目录的Scripts目录
d:
cd D:\python\Scripts
# 安装 可以使用国内源
pip install ipython
如果安装了 IPython,scrapy终端将使用 IPython (替代标准Python终端)。 IPython 终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。
使用:命令行直接输入ipython,进入的新命令窗口自带高亮及自动补全:
3、使用scrapy shell
# 直接在window的终端中输入scrapy shell 域名
# 直接在命令终端(不需要进入python或者ipython终端),执行完毕之后,自动进入ipython终端
scrapy shell www.baidu.com
可以直接获取到response对象,可以直接在此进行调试。
四、实战:获取当当网商品数据
1、初始化项目
# 在自定义目录中创建项目
scrapy startproject scrapy_dangdang
# 创建爬虫文件
# cd 项目的名字\项目的名字\spiders
cd scrapy_dangdang\scrapy_dangdang\spiders
# scrapy genspider 爬虫文件的名字 要爬取网页
# 中国古典小说网址:http://category.dangdang.com/cp01.03.32.00.00.00.html
scrapy genspider dang http://category.dangdang.com/cp01.03.32.00.00.00.html
# 运行
scrapy crawl dang
2、定义item文件
在项目自动生成的item.py中,定义要爬取的数据格式:
import scrapy
class ScrapyDangdangItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 通俗的说就是你要下载的数据都有什么,固定写法:scrapy.Field()
# 图片
src = scrapy.Field()
# 名字
name = scrapy.Field()
# 价格
price = scrapy.Field()
3、爬取图片、名字、价格
我们先分析这三者的xpath:
拿到图片的xpath://ul[@id="component_59"]/li/a/img/@src
因为图片懒加载,应该取图片的data-original
属性。
拿到标题的xpath://ul[@id="component_59"]/li/p[@class="name"]/a/@title
拿到价格的xpath://ul[@id="component_59"]/li/p[@class="price"]/span[1]/text()
import scrapy
class DangSpider(scrapy.Spider):
name = "dang"
allowed_domains = ["category.dangdang.com"]
start_urls = ["http://category.dangdang.com/cp01.03.32.00.00.00.html"]
def parse(self, response):
# src = //ul[@id="component_59"]/li/a/img/@src
# name = //ul[@id="component_59"]/li/p[@class="name"]/a/@title
# price = //ul[@id="component_59"]/li/p[@class="price"]/span[1]/text()
# 所有的seletor的对象 都可以再次调用xpath方法
li_list = response.xpath('//ul[@id="component_59"]/li')
for li in li_list:
src = li.xpath('./a/img/@data-original').extract_first()
# 第一张图片和其他的图片的标签的属性是不一样的
# 第一张图片的src是可以使用的 其他的图片的地址是data-original
if src:
src = src
else:
src = li.xpath('./a/img/@src').extract_first()
name = li.xpath('./p[@class="name"]/a/@title').extract_first()
price = li.xpath('./p[@class="price"]/span[1]/text()').extract_first()
# 拿到所有信息
print(src + name + price)
4、管道封装
(1)熟悉yield
带有tield的函数不再是一个普通函数,而是一个生成器generator,可用于迭代。
yield是一个类似return的关键字,迭代一次遇到yield时就返回yield后面(右边)的值。重点是:下一次迭代时,从上一次迭代器遇到的yield后面的代码(下一行)开始执行。
简单理解:yield就是return返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始。
(2)构造item对象并交给pipeline
上面我们已经获取到图片、名称和价格了,继续在parse方法中构造item对象并交给pipeline:
from scrapy_dangdang.items import ScrapyDangdangItem
# 构造item对象
book = ScrapyDangdangItem(src=src,name=name,price=price)
# 获取一个book就将book交给pipelines
yield book
(3)在settings.py中开启pipeline
管道可以有很多个 那么管道是有优先级的 优先级的范围是1到1000 值越小优先级越高。
ITEM_PIPELINES = {
# 管道可以有很多个 那么管道是有优先级的 优先级的范围是1到1000 值越小优先级越高
"scrapy_dangdang.pipelines.ScrapyDangdangPipeline": 300,
}
(4)编辑pipeline.py
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
# 如果想使用管道的话 那么就必须在settings中开启管道
class ScrapyDangdangPipeline:
# 在爬虫文件开始的之前就执行的一个方法 :open_spider
def open_spider(self,spider):
self.fp = open('book.json','w',encoding='utf-8')
# 执行过程:process_item
# item就是yield后面的book对象
def process_item(self, item, spider):
# 以下这种模式不推荐 因为每传递过来一个对象 那么就打开一次文件 对文件的操作过于频繁
# # (1) write方法必须要写一个字符串 而不能是其他的对象
# # (2) w模式 会每一个对象都打开一次文件 覆盖之前的内容
# with open('book.json','a',encoding='utf-8')as fp:
# fp.write(str(item))
self.fp.write(str(item))
self.fp.write('\n')
return item
# 在爬虫文件执行完之后 执行的方法 : close_spider
def close_spider(self,spider):
self.fp.close()
(5)执行,查看写入的json文件
scrapy crawl dang
5、多条管道使用
(1)pipelines.py定义管道类
pipelines.py中可以定义多个类,直接写即可。
类中有三个默认的方法,可以直接使用。
import urllib.request
class ScrapyDangdangDownloadPipeline:
def process_item(self, item, spider):
url = 'http:' + item.get('src')
filename = './books/' + item.get('name') + '.jpg'
urllib.request.urlretrieve(url = url, filename= filename)
# 有返回值
return item
(2)settings.py开启管道
ITEM_PIPELINES = {
# 管道可以有很多个 那么管道是有优先级的 优先级的范围是1到1000 值越小优先级越高
"scrapy_dangdang.pipelines.ScrapyDangdangPipeline": 300,
'scrapy_dangdang.pipelines.ScrapyDangdangDownloadPipeline':301
}
(5)执行,查看写入的json文件与图片
先在spiders下创建books目录。
# 执行
scrapy crawl dang
6、获取多页数据
import scrapy
from scrapy_dangdang.items import ScrapyDangdangItem
class DangSpider(scrapy.Spider):
name = "dang"
allowed_domains = ["category.dangdang.com"]
start_urls = ["http://category.dangdang.com/cp01.03.32.00.00.00.html"]
base_url = 'http://category.dangdang.com/cp'
page = 1
def parse(self, response):
# 。。。省略
# 每一页的爬取的业务逻辑全都是一样的,所以我们只需要将执行的那个页的请求再次调用parse方法
# 就可以了
# http://category.dangdang.com/pg2-cp01.03.32.00.00.00.html
# http://category.dangdang.com/pg3-cp01.03.32.00.00.00.html
# http://category.dangdang.com/pg4-cp01.03.32.00.00.00.html
if self.page < 100:
self.page = self.page + 1
url = self.base_url + str(self.page) + '-cp01.03.32.00.00.00.html'
# 怎么去调用parse方法
# scrapy.Request就是scrpay的get请求
# url就是请求地址
# callback是你要执行的那个函数 注意不需要加()
yield scrapy.Request(url=url,callback=self.parse)
五、实战:获取电影天堂不同页面的数据
1、效果
获取电影天堂,第一页列表的电影名:
然后点击电影详情,再从第二页获取到详情中的图片:
2、核心代码
mv.py核心代码
import scrapy
from scrapy_movie_099.items import ScrapyMovie099Item
class MvSpider(scrapy.Spider):
name = 'mv'
allowed_domains = ['www.dygod.net']
start_urls = ['https://www.dygod.net/html/gndy/china/index.html']
def parse(self, response):
# 要第一个的名字 和 第二页的图片
a_list = response.xpath('//div[@class="co_content8"]//td[2]//a[2]')
for a in a_list:
# 获取第一页的name 和 要点击的链接
name = a.xpath('./text()').extract_first()
href = a.xpath('./@href').extract_first()
# 第二页的地址是
url = 'https://www.dygod.net' + href
# 对第二页的链接发起访问 并将name参数传入
yield scrapy.Request(url=url,callback=self.parse_second,meta={'name':name})
def parse_second(self,response):
# 注意 如果拿不到数据的情况下 一定检查你的xpath语法是否正确
src = response.xpath('//div[@id="Zoom"]//img/@src').extract_first()
# 接受到请求的那个meta参数的值
name = response.meta['name']
movie = ScrapyMovie099Item(src=src,name=name)
yield movie
pipelines.py:
from itemadapter import ItemAdapter
class ScrapyMovie099Pipeline:
def open_spider(self,spider):
self.fp = open('movie.json','w',encoding='utf-8')
def process_item(self, item, spider):
self.fp.write(str(item))
return item
def close_spider(self,spider):
self.fp.close()
settings.py中开启pipeline:
ITEM_PIPELINES = {
'scrapy_movie_099.pipelines.ScrapyMovie099Pipeline': 300,
}
六、实战:使用CrawlSpider获取读书网的数据
1、CrawlSpider简介
CrawlSpider继承自scrapy.Spider,可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发送请求。
所以,如果有需要跟进链接的需求,就是爬取了网页之后,需要提取链接再次爬取,使用CrawlSpider是非常合适的。
提取链接常用语法:
链接提取器,在这里就可以写规则提取指定链接:
scrapy.linkextractors.LinkExtractor(
allow = (), # 正则表达式,提取符合正则的链接
deny = (), # (不用)正则表达式 不提取符合正则的链接
allow_domains = (), # (不用)允许的域名
deny_domains = (), # (不用)不允许的域名
restrict_xpaths = (), # xpath,提取符合xpath规则的链接
restrict_css = () # 提取符合选择器规则的链接
)
# 使用实例
# 正则用法:
links = LinkExtractor(allow = r'list_23_\d+\.html')
# xpath:
links = LinkExtractor(restrict_xpaths = r'//div[@class="x"]')
# css用法:
links = LinkExtractor(restrict_css='.x')
# 提取链接
links.extract_links(response)
2、创建项目
# 创建项目:scrapy startproject 项目的名字
scrapy startproject readbook
# 创建爬虫文件
# cd 项目名字\项目名字\spiders
cd readbook/readbook/Spiders
# scrapy genspider -t crawl 爬虫文件的名字 爬取的域名
scrapy genspider -t crawl read www.dushu.com/book/1188_1.html
我们发现,read.py内容和我们之前不太一样了:
3、定义item
class ReadbookItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
name = scrapy.Field()
src = scrapy.Field()
4、提取数据
read.py:
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from readbook.items import ReadbookItem
class ReadSpider(CrawlSpider):
name = "read"
allowed_domains = ["www.dushu.com"]
# 注意,第一个url也要匹配规则!不然会跳过第一页
start_urls = ["https://www.dushu.com/book/1188_1.html"]
# 规则
rules = (Rule(LinkExtractor(allow=r"/book/1188_\d+.html"), callback="parse_item", follow=True),)
# 解析
def parse_item(self, response):
img_list = response.xpath('//div[@class="bookslist"]//img')
for img in img_list:
name = img.xpath('./@data-original').extract_first()
src = img.xpath('./@alt').extract_first()
book = ReadbookItem(name=name,src=src)
yield book
5、定义pipeline
# settings.py
ITEM_PIPELINES = {
"readbook.pipelines.ReadbookPipeline": 300,
}
# pipelines.py
from itemadapter import ItemAdapter
class ReadbookPipeline:
def open_spider(self,spider):
self.fp = open('book.json','w',encoding='utf-8')
def process_item(self, item, spider):
self.fp.write(str(item))
return item
def close_spider(self,spider):
self.fp.close()
6、启动
scrapy crawl read
执行完毕之后,查看book.json文件。
7、保存至mysql
安装pymysql:
# 进入到python安装目录的Scripts目录
d:
cd D:\python\Scripts
# 安装 可以使用国内源
pip install pymysql
添加一个pipeline:
# settings.py
ITEM_PIPELINES = {
"readbook.pipelines.ReadbookPipeline": 300,
# MysqlPipeline
'readbook.pipelines.MysqlPipeline':301
}
# 参数中一个端口号 一个是字符集 都要注意
DB_HOST = '192.168.1.1'
# 端口号是一个整数
DB_PORT = 3306
DB_USER = 'root'
DB_PASSWROD = '123'
DB_NAME = 'spider01'
# utf-8的杠不允许写
DB_CHARSET = 'utf8'
pipelines.py:
# 加载settings文件
from scrapy.utils.project import get_project_settings
import pymysql
class MysqlPipeline:
def open_spider(self,spider):
settings = get_project_settings()
self.host = settings['DB_HOST']
self.port =settings['DB_PORT']
self.user =settings['DB_USER']
self.password =settings['DB_PASSWROD']
self.name =settings['DB_NAME']
self.charset =settings['DB_CHARSET']
self.connect()
def connect(self):
self.conn = pymysql.connect(
host=self.host,
port=self.port,
user=self.user,
password=self.password,
db=self.name,
charset=self.charset
)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
sql = 'insert into book(name,src) values("{}","{}")'.format(item['name'],item['src'])
# 执行sql语句
self.cursor.execute(sql)
# 提交
self.conn.commit()
return item
def close_spider(self,spider):
self.cursor.close()
self.conn.close()
七、实战:发送post请求
关键代码:
import scrapy
import json
class TestpostSpider(scrapy.Spider):
name = 'testpost'
allowed_domains = ['https://fanyi.baidu.com/sug']
# post请求 如果没有参数 那么这个请求将没有任何意义
# 所以start_urls 也没有用了
# parse方法也没有用了
# start_urls = ['https://fanyi.baidu.com/sug/']
#
# def parse(self, response):
# pass
# start_requests是一个固定方法
def start_requests(self):
url = 'https://fanyi.baidu.com/sug'
data = {
'kw': 'final'
}
yield scrapy.FormRequest(url=url,formdata=data,callback=self.parse_second)
def parse_second(self,response):
content = response.text
obj = json.loads(content,encoding='utf-8')
print(obj)