目录
1、搭建Scrapy爬虫框架
1.1 使用Anaconda安装Scrapy
1. 2 Scrapy的基本应用
1.2.1 创建Scrapy项目
1.2.2 创建爬虫
1.2.3 爬取数据
1.3 编写Item Pipeline
1.3.1 项目管道的核心方法
1.3.2 将信息存储到数据库中
1.4 自定义中间件
1.4.1 设置随机请求头
1.4.2 设置Cookies
1.4.3 设置代理IP
1.4.4 文件下载
14. 5 Scrapy规则爬虫
14.5.1 crawlspider
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 startproject scrapyDdemo
目录结构:
scrapy.Spider类中提供了start_requests()方法实现初始化网络请求,然后通过parese()方法解析返回的结果。scrapy.Spider类中的常用属性和方法含义如下:
案例:
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)
说明:运行爬虫文件,有以下几种方式。
在项目下创建一个运行爬虫的入口文件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
# 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
Item Pipeline的用途如下:
在编写自定义Item Pipeline时,可以实现以下几个方法:
说明:
process_item()方法用于处理返回的item对象,在处理时会先处理低优先级的item对象。直到所有的方法调用完毕。如果返回Deferred或引发DropItem异常,那么该item对象将不再进行处理。
scrapy startproject jd
cd jd
scrapy genspider jingdong jd.com
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
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()
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
SQL_HOST = 'localhost'
SQL_USER = 'root'
SQL_PASSWORD = 'root'
SQL_DATABASE = 'mysoft'
SQL_PORT = 3306
ITEM_PIPELINES = {
'jd.pipelines.JdPipeline': 300,
}
Scrapy中内置了多个中间件,不过在大多数情况下开发者都会选择自己创建一个属于自己的中间件,这样既可以满足自己的开发需求,还可以节省很多开发时间。在实现自定义中间件时需要重写部分方法,因为Scrapy引擎需要根据这些方法名来执行并处理,如果没有重写这些方法,Scrapy的引擎将会按照原有的方法执行,从而失去自定义中间件的意义。
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、编写爬虫文件
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'
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,
}
Scrapy中提供了可以专门处理下载的Pipeline(项目管道),其中包括File Pipeline(文件管道)以及Images Pipeline(图片管道),在使用Images Pipeline时可以将所有下载的图片格式转换为JPEG/RGB格式以及设置缩略图。
以继承ImagesPipeline类为例,可以重写以下三个方法。
案例:下载京东外设的商品图片
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的区别。