目录
Splash是一个JavaScript渲染服务,它是一个带有HTTP API的轻型WEB浏览器,Python可以通过HTTP API调用Splash中的一些方法实现对页面的渲染工作。同时还可以使用Lua语言实现页面的渲染,所以使用Splash同样可以实现动态渲染页面的爬取
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量的代码,就能够快速的抓取
Scrapy使用了Twisted异步网络框架,可以加快我们的下载速度
官网:https://scrapy.org/
文档:Scrapy 2.7 documentation — Scrapy 2.7.1 documentation
中文文档:https://scrapy-chs.readthedocs.io/zh_CN/0.24/
包含以下几个部分:
- Scrapy Engine(框架的引擎):用于处理整个系统的数据流,触发各种事件,是整个框架的核心。
- Scheduler(调度器): 用于接收引擎发过来的请求,添加至队列中,在引擎再次请求时将请求返回给引擎。可以理解为从URL队列中取出一个请求地址,同时取出重复的请求地址。
- Downloader(下载器):用于从网络下载Web资源。
- Spiders(网络爬虫):从指定网页中爬取需要的信息。
- Item Pipeline(项目管道):用于处理爬取后的数据,例如数据的清洗,验证以及保存。
- Downloader Middlewares(下载器中间件):位于Scrapy引擎和下载器之间,主要用于处理引擎与下载器之间的网络请求和响应。
- Spider Middlewares(爬虫中间件):位于爬虫和引擎之间,主要用于爬虫的响应输入和请求的输出。
- Scheduler Middewares(调度中间件):位于引擎和调度之间,主要用于处理从引擎发送到调度的请求和响应。
1、搭建Scrapy爬虫框架
1.1 使用Anaconda安装Scrapy
1. 2 Scrapy的基本应用
1.2.1 创建Scrapy项目
scrapy startproject scrapyDdemo
目录结构:
- spiders(文件夹):用于创建爬虫文件,编写爬虫规则
- items文件:用于数据的定义,可以寄存处理后的数据
- middlewares文件:定义爬取时 的中间件,其中包括SpiderMiddleware(爬虫中间件)、DownloaderMiddleware(下载中间件)
- pipelines文件:用于实现清洗数据,验证数据,保存数据
- settings文件:整个框架的配置文件,主要包含配置爬虫信息,请求头,中间件等。
- scrapy.cfg文件:项目部署文件,定义了项目的配置文件路径等相关信息。
1.2.2 创建爬虫
scrapy.Spider类中提供了start_requests()方法实现初始化网络请求,然后通过parese()方法解析返回的结果。scrapy.Spider类中的常用属性和方法含义如下:
- name:用于定义一个爬虫名称的字符串。这个名称必须唯一。因为Scrapy通过这个名字进行爬虫的查找。
- allowed_domains:包含了爬虫允许爬取的域名列表。当OffsiteMiddleware启用时,域名不在列表汇总的URL不会被爬取。
- start_urls:URL的初始列表,如果没有指定特定的URL,爬虫会从该列表中进行爬取。
- custom_settings:这是一个专属于当前爬虫的配置。是一个字典类型的数据。设置该属性会覆盖整个项目的全局。所以在设置该属性时必须在实例化前更新,必须定义为类变量。
- settings:是一个settings对象,通过它,可以获取项目的全局设置变量。
- logger:使用Spider创建Python日志器
- start_requests():该方法用于生成网络请求,它必须返回一个可迭代对象,该方法默认使用start_urls中的URL来生成request,而request请求方式为GET,如果采用POST方法来请求网页时,可以使用FormRequest()重写该方法。
- parse():如果response没有指定回调函数时,该方法是Scrapy处理response的默认方
- closed():当爬虫关闭时,该函数会被调用,该方法用于代替监听工作,可以定义释放资源和收尾操作。
案例:
import scrapy
from scrapyDemo.items import ScrapydemoItem
class QuoteSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com']
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/'
]
for url in urls:
yield scrapy.Request(url,callback=self.parse)
def parse(self, response):
page = response.url.split('/')[-2]
filename = f'quotes-{page}.html'
with open(filename,'wb') as file:
file.write(response.body)
self.log(f'Saved file {filename}')
import scrapy
import json
class QuoteSpider(scrapy.Spider):
name = 'quote1'
data = {
'1':'能力是有限的',
'2':'星光不问赶路人'
}
def start_requests(self):
return [scrapy.FormRequest('http://httpbin.org/post',formdata=self.data,callback=self.parse)]
def parse(self, response):
response_dict = json.loads(response.text)
print(response_dict)
说明:运行爬虫文件,有以下几种方式。
- 在命令框输入“scrapy crawl quotes”
- Scrapy提供了一个可以在程序中启动的API,也就是CrawlerProcess类。
在项目下创建一个运行爬虫的入口文件run.py
# 导入CrawlerProcess类
from scrapy.crawler import CrawlerProcess
# 导入获取项目设置信息
from scrapy.utils.project import get_project_settings
if __name__ == '__main__':
# 创建CrawlerProcess类对象并传入项目设置信息参数
process = CrawlerProcess(get_project_settings())
# 设置需要启动的爬虫名称
process.crawl('quote')
# 启动爬虫
process.start()
另外一种方法,模拟cmd命令
from scrapy import cmdline
cmdline.execute('scrpay crawl quote1'.split())
说明:
cmdline.execute('scrpay crawl quote1'.split())可以导出文件格式,
如导出json格式文件,cmdline.execute('scrapy crawl quotes -o test.json'.split()),可以导出以下几种格式:json', 'jsonlines', 'jsonl', 'jl', 'csv', 'xml', 'marshal', 'pickle
cmd命令:scrapy crawl quotes -o test.jsonlines
1.2.3 爬取数据
-
CSS提取数据
# text = quote.css('span.text::text').extract_first()
# author = quote.css('small.anthor::text').extract_first()
# tags = quote.css('div.tags a.tag::text').extract()
get() 返回一条数据 get_all() 返回多条数据
# text = quote.css('span.text::text').get()
# author = quote.css('small.author::text').get()
# tags = quote.css('div.tags a.tag::text').getall()
XPath提取数据
response.xpath('//title/text()').extract_first()
response.xpath('./span[@class="text"]/text()').get()
response.xpath('.//small[@class="author"]/text()').get()
response.xpath('.//div[@class="tags"]/a[@class="tag"]/text()').getall()
说明:
Scrapy的选择对象中还提供了.re()方法。通过response.xpath().re()方式来调用,.re()方法中传入对应的正则表达式即可。
翻页提取数据:
def parse(self, response):
for quote in response.xpath('.//*[@class="quote"]'):
author = quote.xpath('.//*[@class="author"]/text()').extract_first()
print(author)
for href in response.css('li.next a::attr(href)'):
yield response.follow(href,self.parse)
说明:
li.next class="next"的li标签
li.next a :中间加一个空格是表示下级
a::attr(href) :a标签的属性href
class ScrapydemoItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()
回到编写的爬虫代码中,在parse()方法中创建item对象,然后返回item信息。
def parse(self, response):
for quote in response.xpath('//*[@class="quote"]'):
text = quote.xpath('.//*[@class="text"]/text()').extract_first()
author = quote.xpath('.//*[@class="author"]/text()').extract_first()
tags = quote.xpath('.//*[@class="tag"]/text()').extract()
item = QuotesItem(text=text,author=author,tags=tags)
yield item
这里也可以这么写:
def parse(self, response):
for quote in response.xpath('//*[@class="quote"]'):
item = ScrapydemoItem()
item['text'] = quote.xpath('.//*[@class="text"]/text()').extract_first()
item['author'] = quote.xpath('.//*[@class="author"]/text()').extract_first()
item['tags'] = quote.xpath('.//*[@class="tag"]/text()').extract()
yield item
1.3 编写Item Pipeline
1.3.1 项目管道的核心方法
Item Pipeline的用途如下:
- 清理HTML数据
- 验证抓取的数据(检查项目是否包含某些字段)
- 检查重复项(并将其删除)
- 将爬取的结果存储到数据库中
在编写自定义Item Pipeline时,可以实现以下几个方法:
- process_item():该方法在自定义Item Pipeline时,所必须实现的方法。提供了两个参数:
- item参数为item对象(被处理的Item)或字典。
- Spider参数为Spider对象(爬取信息的爬虫)
说明:
process_item()方法用于处理返回的item对象,在处理时会先处理低优先级的item对象。直到所有的方法调用完毕。如果返回Deferred或引发DropItem异常,那么该item对象将不再进行处理。
- open_spider():该方法是在开启爬虫时被调用,所以在这个方法中可以进行初始化操作,其中spider参数就是被开启的spider(爬虫)对象。
- close_spider():是在关闭爬虫时被调用的,在这个方法中可以进行一些收尾工作,其中spider参数就是被开启的spider(爬虫)对象。
- from_crawler():该方法为类方法。需要使用@classmethod进行标识,在调用该方法时需要通过参数cls创建实例对象,最后需要返回这个实例对象,通过crawler参数可以获取scrapy所有的核心组件,例如配置信息等。
1.3.2 将信息存储到数据库中
案例:爬取京东书籍
- 创建项目
scrapy startproject jd
- 创建爬虫
cd jd
scrapy genspider jingdong jd.com
- spiders文件
jingdong.py:
import scrapy
from jd.items import JdItem
import json
class JingdongSpider(scrapy.Spider):
name = 'jingdong'
allowed_domains = ['jd.com']
start_urls = ['http://jd.com/']
def start_requests(self):
url = 'https://gw-e.jd.com/client.action?callback=func&body=%7B%22moduleType%22%3A1%2C%22page%22%3A4%2C%22pageSize%22%3A20%2C%22scopeType%22%3A1%7D&functionId=bookRank&client=e.jd.com&_=1650724495119'
yield scrapy.Request(url=url,callback=self.parse)
def parse(self, response):
try:
data = response.text.lstrip('func(')
data = data.rstrip(')')
data = json.loads(data)
except Exception as error:
print('error:',error)
all_books = data['data']['books']
for book in all_books:
item = JdItem()
item['bookName'] = book['bookName']
item['sellPrice'] = book['sellPrice']
item['authors'] = book['authors']
item['coverUrl'] = book['coverUrl']
item['bookId'] = book['bookId']
item['definePrice'] = book['definePrice']
item['publisher'] = book['publisher']
item['discount'] = book['discount']
yield item
- 创建items.py
class JdItem(scrapy.Item):
bookName = scrapy.Field()
sellPrice = scrapy.Field()
authors = scrapy.Field()
coverUrl = scrapy.Field()
bookId = scrapy.Field()
definePrice = scrapy.Field()
publisher = scrapy.Field()
discount = scrapy.Field()
- 编写Item Pipeline
def __init__(self,host,database,user,password,port):
self.host = host
self.database = database
self.user = user
self.password = password
self.port = port
@classmethod
def from_crawler(cls, crawler):
return cls(
host=crawler.settings.get('SQL_HOST'),
user=crawler.settings.get('SQL_USER'),
password=crawler.settings.get('SQL_PASSWORD'),
database=crawler.settings.get('SQL_DATABASE'),
port=crawler.settings.get('SQL_PORT'),
)
def open_spider(self,spider):
self.db = pymysql.connect(host=self.host,user=self.user,password=self.password,port=self.port,database=self.database,autocommit=True)
self.cursor = self.db.cursor() # 创建游标
def clost_spider(self,spider):
self.db.close()
def process_item(self, item, spider):
data = dict(item)
try:
authors = json.dumps(data['authors'])
query = """insert into cmf_books (bookName,sellPrice,authors,coverUrl,bookId,definePrice,publisher,discount) values (%s,%s,%s,%s,%s,%s,%s,%s)"""
values = (
str(data["bookName"]), str(data["sellPrice"]), str(authors), str(data["coverUrl"]), str(data["bookId"]),
str(data["definePrice"]), str(data["publisher"]), str(data["discount"]))
self.cursor.execute(query, values)
self.db.commit()
except Exception as error:
print('process_item_error:',error)
return item
- settings.py配置文件
SQL_HOST = 'localhost'
SQL_USER = 'root'
SQL_PASSWORD = 'root'
SQL_DATABASE = 'mysoft'
SQL_PORT = 3306
ITEM_PIPELINES = {
'jd.pipelines.JdPipeline': 300,
}
1.4 自定义中间件
Scrapy中内置了多个中间件,不过在大多数情况下开发者都会选择自己创建一个属于自己的中间件,这样既可以满足自己的开发需求,还可以节省很多开发时间。在实现自定义中间件时需要重写部分方法,因为Scrapy引擎需要根据这些方法名来执行并处理,如果没有重写这些方法,Scrapy的引擎将会按照原有的方法执行,从而失去自定义中间件的意义。
1.4.1 设置随机请求头
1、爬虫文件
class Demo1Spider(scrapy.Spider):
name = 'demo1'
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/' ]
for url in urls:
yield scrapy.Request(url=url,callback=self.parse)
def parse(self, response):
print('请求信息为:',response.request.headers.get('User-Agent'))
2、中间件
先导入fake-useragent模块中的UserAgent类,然后创建RandomHeaderMiddleware类并通过init()函数进行类的初始化工作。
from fake_useragent import UserAgent
class RandomHeaderMiddleware(object):
def __init__(self,crawler):
self.ua = UserAgent()
self.type = crawler.settings.get('RANDOM_UA_TYPE','chrome')
@classmethod
def from_crawler(cls,crawler):
return cls(crawler)
def process_request(self,request,spider):
request.headers.setdefault('User-Agent',getattr(self.ua,self.type))
说明:
# 若settings中没有设置RANDOM_UA_TYPE的值默认值为random,
# 从settings中获取RANDOM_UA_TYPE变量,值可以是 random ie chrome firefox safari opera msie
3、打开settings.py文件 配置以下信息
DOWNLOADER_MIDDLEWARES = {
'demo.middlewares.RandomHeaderMiddleware': 400,
'demo.middlewares.DemoDownloaderMiddleware': None,
}
RANDOM_UA_TYPE = 'random' # 这里的键名可以任意,但是值只能为random
说明:
本次中间件中的重点是需要重写process_request()方法,参数request表示当前的请求,例如请求头,请求方式以及请求地址等信息。参数spider表示爬虫程序。该方法返回的具体说明如下:
None:最常见的返回值,表示该方法已经执行完成并向下执行爬虫程序。
response:停止该方法的执行,开始执行process_response()方法
request:停止当前的中间件,将当前的请求交给Scrapy引擎重新执行。
IgnoreRequest:抛出异常对象,再通过process_exception()方法处理异常,结束当前的万国请求。
1.4.2 设置Cookies
案例:
1、编写爬虫文件
import scrapy
class HttpbinSpider(scrapy.Spider):
name = 'httpbin'
allowed_domains = ['httpbin.org']
start_urls = ['http://httpbin.org/get']
def start_requests(self):
yield scrapy.Request(url=self.start_urls[0],callback=self.parse)
def parse(self, response):
print('*' * 200)
print(response.text)
2、在middelwares.py文件中,定义用于格式化与设置的Cookies的中间件。
class CookieTestDownloaderMiddleware:
def __init__(self,cookies_str):
self.cookies_str = cookies_str
@classmethod
def from_crawler(cls,crawler):
return cls(
cookies_str = crawler.settings.get('COOKIES_DEMO')
)
cookies = {}
def process_request(self,request,spider):
for cookie in self.cookies_str.split(';'):
key,value = cookie.split('=',1)
self.cookies.__setitem__(key,value)
request.cookies = self.cookies
from fake_useragent import UserAgent
class RandomHeaderDownloaderMiddleware(object):
def __init__(self,crawler):
self.ua = UserAgent()
self.type = crawler.settings.get('RANDOM_TYPE','chrome')
@classmethod
def from_crawler(cls,crawler):
return cls(crawler)
def process_request(self,request,spider):
request.headers.setdefault('User-Agent',getattr(self.ua,self.type))
3、打开settings.py文件,将DOWNLOADER_MIDDLEWARES配置信息中的默认信息禁用。然后添加用于处理Cookies与随机请求头的配置信息并激活,最后定义从浏览器中获取的Cookies信息。
DOWNLOADER_MIDDLEWARES = {
'cookie.middlewares.CookieTestDownloaderMiddleware': 1,
'cookie.middlewares.RandomHeaderDownloaderMiddleware': 2,
'cookie.middlewares.CookieDownloaderMiddleware': 543,
}
# 定义从浏览器中获取的Cookies
COOKIES_DEMO = 'bid=tE6HN3Ew1o4;...'
RANDOM_TYPE = 'random'
1.4.3 设置代理IP
class HttpbinSpider(scrapy.Spider):
name = 'httpbin'
allowed_domains = ['httpbin.org']
start_urls = ['http://httpbin.org/']
def start_requests(self):
yield scrapy.Request('http://httpbin.org/get',callback=self.parse)
def parse(self, response):
print(response.text)
2、编写随机ip中间件
import random
class IpRandomProxyDownloaderMiddleware(object):
proxy = [
'222.179.155.90:9091','112.5.56.2:9091','103.117.192.14:80'
]
def process_request(self,request,spider):
proxy = random.choice(self.proxy)
request.meta['proxy'] = 'http://' + proxy
3、设置settings
DOWNLOADER_MIDDLEWARES = {
'proxy.middlewares.IpRandomProxyDownloaderMiddleware': 1,
'proxy.middlewares.ProxyDownloaderMiddleware': None,
}
1.4.4 文件下载
Scrapy中提供了可以专门处理下载的Pipeline(项目管道),其中包括File Pipeline(文件管道)以及Images Pipeline(图片管道),在使用Images Pipeline时可以将所有下载的图片格式转换为JPEG/RGB格式以及设置缩略图。
以继承ImagesPipeline类为例,可以重写以下三个方法。
- file_path():该方法用于返回指定文件的下载路径,第一request参数是当前下载对应的request对象。
- get_media_requests():该方法中的第一个参数为Item对象,可以通过Item对象获取URL,然后将URL加入请求队列,等待请求。
- item_completed():当单个Item完成下载后的处理方法。通过该方法可以实现筛选下载失败的图片,该方法中的第一个参数results就是当前item对应的下载结果,其中包含下载成功或失败的信息。
案例:下载京东外设的商品图片
14. 5 Scrapy规则爬虫
14.5.1 crawlspider
scrapy genspider -t crawl 爬虫名字 域名
3、LinkExtractors链接提取器
class scrapy.linkextractors.LinkExtractor(
allow = (),
deny = (),
allow_domains = (),
deny_domains = (),
deny_extensions = None,
restrict_xpaths = (),
tags = ('a','area'),
attrs = ('href'),
canonicalize = True,
unique = True,
process_value = None
)
主要参数讲解:
allow:允许的url。所有满足这个正则表达式的url都会被提取。
deny:禁止的url。所有满足这个正则表达式的url都不会被提取。
allow_domains:允许的域名。只有在这个里面指定的域名的url才会被提取。
deny_domains:禁止的域名。所有在这个里面指定的域名的url都不会被提取。
restrict_xpaths:严格的xpath。和allow共同过滤链接。
4、Rule规则类
class scrapy.spiders.Rule(
link_extractor,
callback = None,
cb_kwargs = None,
follow = None,
process_links = None,
process_request = None
)
主要参数讲解:
link_extractor:一个LinkExtractor对象,用于定义爬取规则。
callback:满足这个规则的url,应该要执行哪个回调函数。因为CrawlSpider使用了parse作为回调函数,因此不要覆盖parse作为回调函数自己的回调函数。
follow:指定根据该规则从response中提取的链接是否需要跟进。
process_links:从link_extractor中获取到链接后会传递给这个函数,用来过滤不需要爬取的链接。
说明:
具体关于CrawlSpider的内容会在CrawlSpider文档中介绍其内内容以及与Spider的区别。