scrapy框架
什么是框架?
就是一个集成了很多功能并且具有很强通用性的一个项目模板。
如何学习框架?
专学习框架封装的各种功能的详细用法。
什么是scrapy?
爬虫中封装好的一个明星框架。功能:高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式
scrapy框架的基本使用
环境的安装:
mac or linux:pip install scrapy
windows:
pip install wheel
下载twisted,下载地址为http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
安装twisted: pip install Twisted-17.1.0-cp36-cp36m-win_amd64.whl
pip install pywin32
pip install scrapy
测试:在终端里录入scrapy指令,没有报错即表示安装成功!
创建一个工程:scrapy startproject xxxPro
cd xxxPro
在spiders子目录中创建一个爬虫文件
scrapy genspider spiderName www.xxx.com
执行工程:
scrapy crawl spiderName
默认输出结果中带有所有的日志信息
可以使用:scrapy crawl spiderName --nolog
来不让日志信息显示,但是这样的话,如果出错了,错误信息也将不会显示
推荐:在settings文件中,加上一句:LOG_LEVEL = "ERROR" # 显示指定类型的日志信息
创建项目后,要在settings中做的事:
# UA伪装
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
# 是否遵从ROBOTSTXT协议
ROBOTSTXT_OBEY = False
# 显示指定类型的日志信息
LOG_LEVEL = "ERROR"
数据的解析:
# xpath 返回的是一个列表,但是列表中的元素是一个Selector的对象
# extract()能够把Selector对象中的data数据提取出来
# 如果列表调用了.extract()方法,则表示,将列表中每一个Selector对象的data的值取出,放在列表中
持久化存储
基于终端指令:scrapy crawl 【spiderName】 -o 【filepath】
要求:只可以将parse方法的返回值存储到本地的文本文件中
注意:持久化存储对应的文本文件的类型只可以为:'json','jsonlines','jl','csv','xml','marshal','pickle'.
好处:简介高效便捷
缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
# 用做数据解析,response就是请求成功后返回的响应 # 基于终端指令的储存 def parse(self, response): div_list = response.xpath('//*[@id="content"]/div/div[2]/div') # 储存所有的数据 all_data = [] for div in div_list: # xpath 返回的是一个列表,但是列表中的元素是一个Selector的对象 # extract()能够把Selector对象中的data数据提取出来 author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract() # 如果列表调用了.extract()方法,则表示,将列表中每一个Selector对象的data的值取出,放在列表中 content = div.xpath('./a[1]/div/span//text()').extract() content = "".join(content) dic = { 'content':content, "author":author, } all_data.append(dic) # 返回要持久化存储的数据 return all_data
parse方法-基于终端指令的持久化存储
基于管道:
编码流程:
数据解析
在item类中定义相关的属性
将解析的数据封装存储到item类型的对象
将item类型的对象提交给管道进行持久化存储的操作
在管道类的process_item中要将其接受到的item对象中存储的数据进行持久化存储操作
在配置文件中开启管道,在配置文件中把下面代码的注释去掉:
ITEM_PIPELINES = {
'first.pipelines.FirstPipeline': 300,
}
好处:
通用性强,既可以存在本地文件,又可以存到数据库等
class FirstItem(scrapy.Item): # define the fields for your item here like: author = scrapy.Field() content = scrapy.Field()
item类
# 基于管道的储存 def parse(self, response): div_list = response.xpath('//*[@id="content"]/div/div[2]/div') # 储存所有的数据 for div in div_list: # xpath 返回的是一个列表,但是列表中的元素是一个Selector的对象 # extract()能够把Selector对象中的data数据提取出来 author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()[:-1] # 如果列表调用了.extract()方法,则表示,将列表中每一个Selector对象的data的值取出,放在列表中 content = div.xpath('./a[1]/div/span//text()').extract() content = "".join(content) # 实例化一个item对象 item = FirstItem() # 把数据封装到item对象中 item["author"] = author item["content"] = content # 把item对象提交到管道中 yield item
parse方法
class FirstPipeline: fp = None # 重写父类的方法,该方法只在爬虫开始的时候被调用一次 def open_spider(self,spider): print("开始爬虫") self.fp = open("./qiubai.txt","w",encoding="utf-8") # 该方法用于接收爬虫文件中提交过来的item对象 # 有多少个item对象提交过来就执行多少次process_item方法 # 多用于数据的持久化存储 def process_item(self, item, spider): author = item["author"] content = item["content"] self.fp.write(author+": "+content+"\n") return item # 重写父类的方法,该方法只在爬虫结束的时候被调用一次 def close_spider(self,spider): print("结束爬虫") self.fp.close()
管道
面试题:将爬取到的数据一份存储到本地一份存储到数据库,如何实现?
【注】
管道文件中一个管道类对应的是将数据存储到一种平台
爬虫文件提交的item只会给管道文件中第一个被执行的管道类接受
process_item中的return item表示将item传递给下一个即将被执行的管道类
# 将数据存入本地文件中 class FirstPipeline: fp = None # 重写父类的方法,该方法只在爬虫开始的时候被调用一次 def open_spider(self,spider): print("开始爬虫") self.fp = open("./qiubai.txt","w",encoding="utf-8") # 该方法用于接收爬虫文件中提交过来的item对象 # 有多少个item对象提交过来就执行多少次process_item方法 # 多用于数据的持久化存储 def process_item(self, item, spider): author = item["author"] content = item["content"] self.fp.write(author+": "+content+"\n") return item # item就会传递给下一个即将被执行的管道类 # 重写父类的方法,该方法只在爬虫结束的时候被调用一次 def close_spider(self,spider): print("结束爬虫") self.fp.close() ''' 爬虫文件提交的item类型的对象最终会提交给哪一个管道类? 答:先执行的管道类(优先级高的管道类) ''' import pymysql #管道文件中的一个管道类对应将一组数据存储到一个平台或者载体中 # 将数据存入数据库中 class MysqlPileLine(object): conn = None cursor = None def open_spider(self,spider): self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='1212', db="spider",charset="utf8") def process_item(self,item,spider): self.cursor = self.conn.cursor() try: self.cursor.execute('insert into qiubai(author,content) values("%s" ,"%s")'%(item["author"],item["content"])) self.conn.commit() except Exception as e: print(e) self.conn. rollback() return item def close_spider(self,spider): self.cursor.close() self.conn.close()
管道类
item类和数据解析的parse方法都用的上面的代码
需要注意的是:在settings.py文件中的ITEM_PIPELINES列表中添加上在管道类中新建的存储到MuSQL数据库的类
# 开启管道 # 300表示的是优先级,数值越小,优先级越高 ITEM_PIPELINES = { 'first.pipelines.FirstPipeline': 300, 'first.pipelines.MysqlPileLine': 301, }
ITEM_PIPELINES列表
基于Spider的全站数据爬取
就是将网站中某板块下的全部页码对应的页面数据进行爬取
需求:爬取校花网中的照片的名称
实现方式:
将所有页面的url添加到start_urls列表(不推荐)
自行手动进行请求发送(推荐)
手动请求发送:
yield scrapy.Request(url,callback):callback专门用做于数据解析
遇到的问题:
调试的时候,发现回调函数 parse_detail 没有被调用,这可能就是被过滤掉了,
两种方法能够使 requests 不被过滤:
1. 在 allowed_domains 中加入 url
2. 在 scrapy.Request() 函数中将参数 dont_filter=True 设置为 True
start_urls = ['http://www.521609.com/meinvxiaohua/',] # 每页都通用的url url = "http://www.521609.com/meinvxiaohua/list12%d.html" page_num = 2 def parse(self, response): li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: img_name = li.xpath('./a[2]/b/text() | ./a[2]/text()').extract_first() print(img_name) if self.page_num <= 10: print("第"+str(self.page_num)+"页") new_url = format(self.url%self.page_num) print(new_url) self.page_num += 1 # 对new_url手动发送请求,回调paese函数 yield scrapy.Request(url=new_url,callback=self.parse)
爬取校花网11页的图片的名称
scrapy五大核心组件简介
引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回.
可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。
用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。
当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
图片示例:
请求传参
使用场景:如果爬取解析的数据不在同一张页面中。(深度爬取)
需求:吧boss的岗位名称,岗位描述
import scrapy from bossPro.items import BossproItem class BossSpider(scrapy.Spider): name = 'boss' allowed_domains = ['www.xxx.com'] start_urls = ['https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position='] url = "https://www.zhipin.com/c101010100/?query=python&page=%d" page_num = 2 # 回调函数接受meta的参数 def parse_detail(self,response): item = response.meta["item"] job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract() job_desc = "".join(job_desc) print(job_desc) item["job_desc"] = job_desc # 提交给管道 yield item def parse(self, response): print(response) li_list = response.xpath('/html/body/div[1]/div[3]/div/div[2]/ul/li') print(li_list) for li in li_list: item = BossproItem() job_name = li.xpath('./div[@class="job-title"]/span[1]/a/text()').extract_first() item["job_name"] = job_name detail_url = 'https://www.zhipin.com'+li.xpath('./div[@class="job-title"]/span[1]/a/@href').extract_first() print(job_name,detail_url) # 请求传参: meta={},可以将字典中的值,作为参数传给回调函数 yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={"item":item}) # 分页操作 if self.page_num <= 5: new_url = format(self.url%(self.page_num)) print(new_url) self.page_num += 1 yield scrapy.Request(url=new_url, callback=self.parse)
请求传参
图片数据爬取之ImagesPipeline
基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别?
字符串:只需要基于xpath进行解析且提交管道进行持久化存储
图片:xpath解析出图片src的属性值。单独的对图片地址发起请求获取图片二进制类型的数据
ImagesPipeline:
只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制数据,并会进行持久化储存
需求:爬取站长素材中的高清图片
使用流程:
数据解析(图片的地址)
将存储图片地址的item提交到制定的管道类
在管道文件中自定制一个基于ImagesPipeLine的一个管道类,重写以下方法:
get_media_request
file_path
item completed
在配置文件中:
指定图片存储的目录:IMAGES_STORE = './imgs bobo'
指定开启的管道:自定制的管道类
class ImagesPipeline(ImagesPipeline): # 对图片的数据进行请求 def get_media_requests(self, item, info): yield scrapy.Request(url=item["src"]) # 定制图片储存的名称 def file_path(self, request, response=None, info=None): # 从图片的url中获得图片的名称 file_name = request.url.split('/')[-1] return file_name def item_completed(self, results, item, info): # 该返回值会传递给下一个即将被执行的管道类 return item
爬取图片的管道类
def parse(self, response): div_list = response.xpath('//*[@id="container"]/div') for div in div_list: # 使用伪属性src2 img_src = div.xpath('./div/a/img/@src2').extract_first() item = ImgsproItem() item["src"] = img_src yield item
爬取图片数据解析的方法
中间件
下载中间件
位置:引擎和下载器之间
作用:批量拦截到整个工程中所有的请求和响应
开启中间件:在settings中把以下代码的注释去掉:
DOWNLOADER_MIDDLEWARES = {
'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}
拦截请求:
UA伪装:process_request
代理IP:process_exception: return request
import random class ImgsproDownloaderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the downloader middleware does not modify the # passed objects. @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ] # 拦截请求 def process_request(self, request, spider): # UA伪装 request.headers["User_Agent"] = random.choice(self.user_agent_list) return None # 拦截响应 def process_response(self, request, response, spider): # Called with the response returned from the downloader. # Must either; # - return a Response object # - return a Request object # - or raise IgnoreRequest return response # 可被选用的代理IP PROXY_http = [ '153.180.102.104:80', '195.208.131.189:56055', ] PROXY_https = [ '120.83.49.90:9000', '95.189.112.214:35508', ] # 拦截发生异常的请求 def process_exception(self, request, exception, spider): # 代理ip if request.url.split(":") == "http": request.meta["proxy"] = "http://"+random.choice(self.PROXY_http) else: request.meta["proxy"] = "https://"+random.choice(self.PROXY_https) # 将修正后的请求,重新的发送 return request def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
中间件之拦截请求
拦截响应:
篡改响应数据,响应对象
需求:爬取网易新闻中的新闻数据(标题和内容)
1.通过网易新闻的首页解析出五大板块对应的详情页的url(没有动态加载)
2.每一个桐决对应的新闻标题都是动态加载出来的(动态加载)
3.通过解析出每一条新闻详情贡的url获取详情页的页面源码,解析出新闻内容
start_urls = ['http://news.163.com/'] model_list = [] # 初始化 def __init__(self): self.bro = webdriver.Chrome(executable_path='./chromedriver.exe') # 解析五大板块的url及请求的发送 def parse(self, response): li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li') alist = [3,4,6,7,8] # 获取对应板块的url链接 for i in alist: a_href = li_list[i].xpath('./a/@href').extract_first() self.model_list.append(a_href) # 对每一个板块的url进行请求的发送 for url in self.model_list: yield scrapy.Request(url=url,callback=self.parse_model) # 解析每一个板块新闻的内容和标题的方法 def parse_model(self,response): div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div') for div in div_list: title = div.xpath('./div//h3/a/text()').extract_first() new_detail_url = div.xpath('./div//h3/a/@href').extract_first() if title and new_detail_url: item = WangyiproItem() item["title"] = title yield scrapy.Request(url=new_detail_url,callback=self.parse_detail,meta={"item":item}) def parse_detail(self,response): content = response.xpath('//*[@id="content"]//text()').extract() content = "".join(content) item = response.meta["item"] item["content"] = content yield item # 重写父类的方法,关闭浏览器对象 def closed(self, spider): self.bro.quit()
数据解析parse方法
class WangyiproPipeline: fp = None # 重写父类的方法,该方法只在爬虫开始的时候被调用一次 def open_spider(self,spider): print("开始爬虫") self.fp = open("./wangyi_news.txt","w",encoding="utf-8") # # # 该方法用于接收爬虫文件中提交过来的item对象 # # 有多少个item对象提交过来就执行多少次process_item方法 # # 多用于数据的持久化存储 def process_item(self, item, spider): title = item["title"] content = item["content"] self.fp.write(title + ": " + content + "\n") return item # item就会传递给下一个即将被执行的管道类 # 重写父类的方法,该方法只在爬虫结束的时候被调用一次 def close_spider(self,spider): print("结束爬虫") self.fp.close()
管道类
import scrapy class WangyiproItem(scrapy.Item): # define the fields for your item here like: title = scrapy.Field() content = scrapy.Field() pass
item类
from scrapy.http import HtmlResponse import time class WangyiproDownloaderMiddleware: def process_request(self, request, spider): return None # 拦截五大板块的响应,加上动态加载的数据 def process_response(self, request, response, spider): # 获取了在爬虫类中的浏览器对象 bro = spider.bro # 挑选出指定的响应对象进行修改 # 通过url指定request # 通过request指定response # spider是在wangyi.py文件中的爬虫对象,在其中拿出model_list列表,存放的是五大板块的url if request.url in spider.model_list: # response五大板块对应的响应对象 # 针对定位到的这些response进行篡改 # 实例化一个新的响应对象(符合需求:包含动态加载出的新闻数据),替代原来旧的响应对象 # 如何获取动态加载出来的数据? bro.get(request.url) time.sleep(1) page_text = bro.page_source # 包含了动态加载的数据 new_response = HtmlResponse(url=request.url, body=page_text,encoding="utf-8",request=request) return new_response else: # response其他请求对应的响应对象 # 返回旧的响应对象 return response def process_exception(self, request, exception, spider): pass
中间件之拦截响应
crawlSpider:类,Spider的一个子类
全站数据爬取的方式
基于Spider:手动请求
基于CrawlSpider
CrawlSpider的使用:
创建一个工程
cd xxx
创建爬虫文件(CrawlSpider):
scrapy genspider -t crawl xxx www.xxx.com
链接提取器:
作用:根据指定的规则(allow)进行指定链接的提取
规则解析器:
作用:将链接提取器提取到的链接进行指定规则(callback)的解析
class SunSpider(CrawlSpider): name = 'sun' allowed_domains = ['www.xxx.com'] start_urls = ['http://www.521609.com/meinvxiaohua/'] # 链接提取器:根据指定的规则(allow="正则表达式")进行指定链接的提取 link = LinkExtractor(allow=r'list12\d+.html') rules = ( # 规则解析器:将链接提取器提取到的链接进行指定规则(callback)的解析操作 Rule(link, callback='parse_item', follow=False), # follow=True:可以将链接提器继续作用到链接提取器提取到的链接对应的页面上 ) def parse_item(self, response): print(response) li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: img_name = li.xpath('./a[2]/b/text() | ./a[2]/text()').extract_first() print(img_name)
CrawlSpiser的全站数据爬取
今天的文章爬虫scrapy框架课程论文_py好用的爬虫框架分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/52005.html