建设网站需要那些技术人员,c 网站开发技术,中国的网站域名,做网站的叫什么持久化
# 爬回来#xff0c;解析完了#xff0c;想存储#xff0c;有两种方案
## 方案一#xff1a;一般不用 parse必须有return值#xff0c;必须是列表套字典形式---使用命令#xff0c;可以保存到json格式中#xff0c;csv中scrapy crawl cnblogs -o cnbogs.j…持久化
# 爬回来解析完了想存储有两种方案
## 方案一一般不用 parse必须有return值必须是列表套字典形式---使用命令可以保存到json格式中csv中scrapy crawl cnblogs -o cnbogs.json #以json形式保存scrapy crawl cnblogs -o cnbogs.csv #以csv形式保存#### 方案二 我们用的使用pipline存储---》可以存到多个位置-第一步在item.py中写一个类class FirstscrapyItem(scrapy.Item):title scrapy.Field()author_img scrapy.Field()author_name scrapy.Field()desc scrapy.Field()url scrapy.Field()# 博客文章内容但是暂时没有content scrapy.Field()-第二步在pipline.py中写代码写一个类open_spide,close_spiderprocess_item-open_spide开启爬虫会触发-close_spider爬完会触发-process_ite:每次要保存一个对象会触发class FirstscrapyFilePipeline:def open_spider(self, spider):print(我开了)self.fopen(a.txt,w,encodingutf-8)def close_spider(self, spider):print(我关了)self.f.close()# 这个很重要def process_item(self, item, spider):self.f.write(item[title]\n)return item-第三步配置文件配置ITEM_PIPELINES {firstscrapy.pipelines.FirstscrapyFilePipeline: 300, # 数字越小优先级越高}-第四步在解析方法parse中yield item对象
全站爬取cnblgos
# 继续爬取下一页
# 爬取文章详情# Request创建在parse中for循环中创建Request对象时传入meta# item对象一定要在for循环中创建否则当前页面都用同一个item导致同一页数据都一样yield Request(urlurl, callbackself.detail_parse,meta{item:item})
# 在parser_detail中取出来itemresponse.meta.get(item)
# Response对象detail_parse中通过response取出meta取出item把文章详情写入def parser_detail(self,response):# content response.css(#cnblogs_post_body).extract_first()itemresponse.meta.get(item)contentstr(response.xpath(//div[idcnblogs_post_body]).extract_first())item[content]contentyield item
cnblogs.py
import scrapy
from scrapy import Request
# from scrapy.http.request import Request
from mysfirstscrapy.items import CnblogsItem# 爬虫类继承了scrapy.Spider
class CnblogsSpider(scrapy.Spider):name cnblogs # 爬虫名字allowed_domains [www.cnblogs.com] # 允许爬取的域---》start_urls [http://www.cnblogs.com/] # 开始爬取的地址def parse(self, response):# item CnblogsItem() #会有问题,是个引用类型article_list response.xpath(//article[contains(class,post-item)]) # 列表中放对象print(len(article_list))for article in article_list:item CnblogsItem() #每次新造一个对象title article.xpath(.//a/text()).extract_first()desc article.xpath(.//p[contains(class,post-item-summary)]/text()).extract()real_desc desc[0].replace(\n, ).replace( , )if real_desc:desc real_descelse:real_desc desc[1].replace(\n, ).replace( , )desc real_descauthor_img article.xpath(.//p//img/src).extract_first()author_name article.xpath(.//footer//span/text()).extract_first()url article.xpath(.//div[contains(class,post-item-text)]//a/href).extract_first()item[title] titleitem[desc] descitem[author_img] author_imgitem[author_name] author_nameitem[url] urlyield Request(urlurl,callbackself.parser_detail,meta{item:item}) # 详情nexthttps://www.cnblogs.comresponse.xpath(//div[contains(class,pager)]/a[last()]/href).extract_first()print(next) # 拿到地址继续爬取组装成一个Request对象#callback 参数是控制返回response后使用的解析方法yield Request(urlnext,callbackself.parse) # 下一页地址继续爬取解析还是用parsedef parser_detail(self,response):# content response.css(#cnblogs_post_body).extract_first()itemresponse.meta.get(item)contentstr(response.xpath(//div[idcnblogs_post_body]).extract_first())item[content]contentyield itemitems.py
# django模型类
class CnblogsItem(scrapy.Item):# title, desc, author_img, author_name, urltitle scrapy.Field()desc scrapy.Field()author_img scrapy.Field()author_name scrapy.Field()url scrapy.Field()#------文章详情暂时没有-----content scrapy.Field()
piplines.py
class MyCnblogsMySqlPipeline:def open_spider(self, spider):self.count0print(我开了)self.conn pymysql.connect(userroot, # The first four arguments is based on DB-API 2.0 recommendation.password123,host127.0.0.1,port3306,databasecnblogs)self.cursor self.conn.cursor()def close_spider(self, spider):print(我关了)self.cursor.close()self.conn.close()def process_item(self, item, spider):print(我来了-----)self.count1print(self.count)sqlinsert into article (title,url,desc,author_name,author_img,content) values (%s,%s,%s,%s,%s,%s)self.cursor.execute(sql,args[item[title],item[url],item[desc],item[author_name],item[author_img],item[content]])self.conn.commit()return item
settings.py
ITEM_PIPELINES {# mysfirstscrapy.pipelines.MyCnblogsPipeline: 300,mysfirstscrapy.pipelines.MyCnblogsMySqlPipeline: 301,
}爬虫中间件和下载中间件 # 爬虫中间件爬虫和引擎之间-用的很少了解即可# 下载中间件引擎和下载器之间-用的多能干啥-进来request对象-加代理-加cookie-加请求头-出去response对象-修改响应对象最后进入到爬虫的parser中就是修改后的response# 爬虫中间件 (了解) middlewares.py
class MysfirstscrapySpiderMiddleware:classmethoddef from_crawler(cls, crawler):# This method is used by Scrapy to create your spiders.s cls()crawler.signals.connect(s.spider_opened, signalsignals.spider_opened)return sdef process_spider_input(self, response, spider):return Nonedef process_spider_output(self, response, result, spider):for i in result:yield idef process_spider_exception(self, response, exception, spider):passdef process_start_requests(self, start_requests, spider):for r in start_requests:yield rdef spider_opened(self, spider):spider.logger.info(Spider opened: %s % spider.name)# 下载中间件
class MysfirstscrapyDownloaderMiddleware:classmethoddef from_crawler(cls, crawler):# This method is used by Scrapy to create your spiders.s cls()crawler.signals.connect(s.spider_opened, signalsignals.spider_opened)return s# 请求来了执行def process_request(self, request, spider):# 返回值可以是如下# return None:继续处理本次请求执行执行下一个中间件的process_request#return Response执行当前中间件的process_response回去进入到引擎被调度进入第6步返回到爬虫的解析方法中# return a Request直接返回给引擎被调度进入第2步进入调度器等待下次被调度爬取# raise IgnoreRequest:执行 process_exceptionreturn None# 请求走了def process_response(self, request, response, spider):# 返回如下# return Response 继续往后走进入到引擎被调度到爬虫中解析# return Request 进入到引擎被调度进调度器# - or raise IgnoreRequest会执行process_exceptionreturn responsedef process_exception(self, request, exception, spider):# Called when a download handler or a process_request()# (from other downloader middleware) raises an exception.# Must either:# - return None: continue processing this exception# - return a Response object: stops process_exception() chain# - return a Request object: stops process_exception() chainpassdef spider_opened(self, spider):spider.logger.info(Spider opened: %s % spider.name)# 在配置文件中配置scrapy加代理cookieheader
加代理
# 在下载中间件的def process_request(self, request, spider):写代码# 第一步-在下载中间件写process_request方法def get_proxy(self):import requestsres requests.get(http://127.0.0.1:5010/get/).json()if res.get(https):return https:// res.get(proxy)else:return http:// res.get(proxy)def process_request(self, request, spider):request.meta[proxy] self.get_proxy()return None# 第二步代理可能不能用会触发process_exception在里面写def process_exception(self, request, exception, spider):print(-----,request.url) # 这个地址没有爬return request
加cookie,修改请求头随机生成UserAgent #### 加cookiedef process_request(self, request, spider):print(request.cookies)request.cookies[name]lqzreturn None # 修改请求头def process_request(self, request, spider):print(request.headers)request.headers[referer] http://www.lagou.comreturn None # 动态生成User-agent使用def process_request(self, request, spider):# fake_useragent模块from fake_useragent import UserAgentua UserAgent()request.headers[User-Agent]str(ua.random)print(request.headers)return None
scrapy集成selenium # 使用scrapy默认下载器---》类似于requests模块发送请求不能执行js有的页面拿回来数据不完整 # 想在scrapy中集成selenium获取数据更完整获取完后自己组装成 Response对象就会进爬虫解析现在解析的是使用selenium拿回来的页面数据更完整 # 集成selenium 因为有的页面是执行完js后才渲染完必须使用selenium去爬取数据才完整# 保证整个爬虫中只有一个浏览器器
# 只要爬取 下一页这种地址使用selenium爬取详情继续使用原来的# 第一步在爬虫类中写
from selenium import webdriver
class CnblogsSpider(scrapy.Spider):bro webdriver.Chrome(executable_path./chromedriver.exe)bro.implicitly_wait(10)def close(spider, reason):spider.bro.close() #浏览器关掉# 第二步在中间件中def process_request(self, request, spider):# 爬取下一页这种地址---》用selenium但是文章详情就用原来的if sitehome/p in request.url:spider.bro.get(request.url)from scrapy.http.response.html import HtmlResponseresponse HtmlResponse(urlrequest.url, bodybytes(spider.bro.page_source, encodingutf-8))return responseelse:return None
源码去重规则(布隆过滤器)
# 如果爬取过的地址就不会再爬了# 调度器可以去重研究一下如何去重的---》使用了集合# 要爬取的Request对象在进入到scheduler调度器排队之前先执行enqueue_request它如果return False这个Request就丢弃掉不爬了----》如何判断这个Request要不要丢弃掉执行了self.df.request_seen(request)它来决定的-----》RFPDupeFilter类中的方法----》request_seen---》会返回True或False----》如果这个request在集合中说明爬过了就return True如果不在集合中就加入到集合中然后返回False# 调度器源码
from scrapy.core.scheduler import Scheduler# 这个方法如果return True表示这个request要爬取如果return False表示这个网址就不爬了(已经爬过了)def enqueue_request(self, request: Request) - bool:# request当次要爬取的地址对象if self.df.request_seen(request):# 有的请情况在爬虫中解析出来的网址不想爬了就就可以指定# yield Request(urlurl, callbackself.detail_parse, meta{item: item},dont_filterTrue)# 如果符合这个条件表示这个网址已经爬过了 return Falsereturn True# self.df 去重类 是去重类的对象 RFPDupeFilter-在配置文件中如果配置了DUPEFILTER_CLASS scrapy.dupefilters.RFPDupeFilter表示使用它作为去重类按照它的规则做去重-RFPDupeFilter的request_seendef request_seen(self, request: Request) - bool:# request_fingerprint 生成指纹fp self.request_fingerprint(request) #request当次要爬取的地址对象#判断 fp 在不在集合中如果在return Trueif fp in self.fingerprints:return True#如果不在加入到集合return Falseself.fingerprints.add(fp)return False# 传进来是个request对象生成的是指纹-爬取的网址https://www.cnblogs.com/teach/p/17238610.html?namelqzage19-和 https://www.cnblogs.com/teach/p/17238610.html?age19namelqz-它俩是一样的返回的数据都是一样的就应该是一条url就只会爬取一次-所以 request_fingerprint 就是来把它们做成一样的(核心原理是把查询条件排序再拼接到后面)-生成指纹指纹是什么 生成的指纹放到集合中去重-www.cnblogs.com?namelqzage19-www.cnblogs.com?age19namelqz-上面的两种地址生成的指纹是一样的# 测试指纹from scrapy.utils.request import RequestFingerprinterfrom scrapy import Requestfingerprinter RequestFingerprinter()request1 Request(urlhttp://www.cnblogs.com?namelqzage20)request2 Request(urlhttp://www.cnblogs.com?age20namelqz)res1 fingerprinter.fingerprint(request1).hex()res2 fingerprinter.fingerprint(request2).hex()print(res1)print(res2)# 集合去重集合中放
# a一个bytes
# 假设爬了1亿条url放在内存中占空间非常大
a6af0a0ffa18a9b2432550e1914361b6bffcff1a
a6af0a0ffa18a9b2432550e191361b6bffc34f1a# 想一种方式极小内存实现去重---》布隆过滤器
# 总结scrapy的去重规则-根据配置的去重类RFPDupeFilter的request_seen方法如果返回True就不爬了如果返回False就爬-后期咱们可以使用自己定义的去重类实现去重# 更小内存实现去重-如果是集合存的数据库越多占内存空间越大如果数据量特别大可以使用布隆过滤器实现去重# 布隆过滤器https://zhuanlan.zhihu.com/p/94668361#bloomfilter是一个通过多哈希函数映射到一张表的数据结构能够快速的判断一个元素在一个集合内是否存在具有很好的空间和时间效率。典型例子爬虫url去重# 原理 BloomFilter 会开辟一个m位的bitArray(位数组)开始所有数据全部置 0 。当一个元素www.baidu.com过来时能过多个哈希函数h1,h2,h3....计算不同的在哈希值并通过哈希值找到对应的bitArray下标处将里面的值 0 置为 1 。# Python中使用布隆过滤器
# 测试布隆过滤器
# 可以自动扩容指定错误率底层数组如果大于了错误率会自动扩容
# from pybloom_live import ScalableBloomFilter
# bloom ScalableBloomFilter(initial_capacity100, error_rate0.001, modeScalableBloomFilter.LARGE_SET_GROWTH)
# url www.cnblogs.com
# url2 www.liuqingzheng.top
# bloom.add(url)
# bloom.add(url2)
# print(url in bloom)
# print(url2 in bloom)from pybloom_live import BloomFilterbf BloomFilter(capacity10)
url www.baidu.com
bf.add(url)
bf.add(aaaa)
bf.add(ggg)
bf.add(deww)
bf.add(aerqaaa)
bf.add(ae2rqaaa)
bf.add(aerweqaaa)
bf.add(aerwewqaaa)
bf.add(aerereweqaaa)
bf.add(we)print(url in bf)
print(wa in bf)# 如果有去重的情况就可以使用集合---》但是集合占的内存空间大如果到了亿级别的数据量想一种更小内存占用而去重的方案----》布隆过滤器
# 布隆过滤器通过不同的hash函数加底层数组实现的极小内存去重
# python中如何使用pybloom_live -指定错误率-指定大小# 使用redis实现布隆过滤器-编译redis---》把第三方扩展布隆过滤器编译进去才有这个功能-https://zhuanlan.zhihu.com/p/94668736# 重写scrapy的过滤类
分布式爬虫
# 原来scrapy的Scheduler维护的是本机的任务队列待爬取的地址本机的去重队列放在集合中---》在本机内存中
# 如果把scrapy项目部署到多台机器上多台机器爬取的内容是重复的# 所以实现分布式爬取的关键就是找一台专门的主机上运行一个共享的队列比如Redis
然后重写Scrapy的Scheduler让新的Scheduler到共享队列存取Request并且去除重复的Request请求所以总结下来实现分布式的关键就是三点#1、多台机器共享队列#2、重写Scheduler让其无论是去重还是任务都去访问共享队列#3、为Scheduler定制去重规则利用redis的集合类型# scrapy-redis实现分布式爬虫-公共的去重-公共的待爬取地址队列# 使用步骤1 把之前爬虫类继承class CnblogsSpider(RedisSpider):2 去掉起始爬取的地址加入一个类属性redis_key myspider:start_urls # redis列表的key后期我们需要手动插入起始地址3 配置文件中配置DUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter # scrapy redis去重类使用redis的集合去重# 不使用原生的调度器了使用scrapy_redis提供的调度器它就是使用了redis的列表SCHEDULER scrapy_redis.scheduler.SchedulerREDIS_HOST localhost # 主机名REDIS_PORT 6379 # 端口ITEM_PIPELINES {# mysfirstscrapy.pipelines.MyCnblogsPipeline: 300,mysfirstscrapy.pipelines.MyCnblogsMySqlPipeline: 301,scrapy_redis.pipelines.RedisPipeline: 400,}# 再不同多台机器上运行scrapy的爬虫就实现了分布式爬虫