js提取指定网站内容 真香系列-JSFinder实用改造
网站优化 • 优采云 发表了文章 • 0 个评论 • 78 次浏览 • 2022-06-22 08:53
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付! 查看全部
js提取指定网站内容 真香系列-JSFinder实用改造
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付!
js提取指定网站内容 真香系列-JSFinder实用改造
网站优化 • 优采云 发表了文章 • 0 个评论 • 52 次浏览 • 2022-06-20 02:45
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付! 查看全部
js提取指定网站内容 真香系列-JSFinder实用改造
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付!
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 66 次浏览 • 2022-06-17 02:02
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 71 次浏览 • 2022-06-13 11:25
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
js提取指定网站内容是什么?如何提升target空间?
网站优化 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-06-08 21:08
js提取指定网站内容,然后存储到数据库,以后访问就可以拉到n页,第一页也一样。这样做当然就会存在大量重复页,可以写个判断,要求用户移除即可。
优点是页面会变得清爽简洁,加载的内容会减少。缺点是当量增加时,服务器压力加大。浏览器延迟加长,页面转化率低。
占有率统计,跳转列表,
人工拉页就ok,这样有可能会造成一个单页面的上拉列表带动很多页面上拉的情况(这个正常,不要理解偏差。要把页面容量定得小点,动态绑定两个事件,直接从api里给他连接a.addeventlistener('target',path.join({scrolltop:1,scrolltopleft:1}),axios.action({expressive:true})),此外,你定的时间点很重要,如果早上发现设定好了时间点出问题,很崩溃,就要纠结下自己设定的时间点的问题了。
额,上面的方法应该是两种,但是题主你加上空格,
如果网站只是内容的增删,就每页增加一个n(n取决于内容量),每页移动一个n个dom(例如只有一个或者空的dom页,所以每页在n个dom中移动)即可。没有必要全部都要自己定义布局。如果涉及到的新增和删除这两个操作都有了并且在数据库里有索引的话可以使用z-index实现。提升空间肯定是有的,但可行性并不高。用jquery的话还能实现批量操作布局。 查看全部
js提取指定网站内容是什么?如何提升target空间?
js提取指定网站内容,然后存储到数据库,以后访问就可以拉到n页,第一页也一样。这样做当然就会存在大量重复页,可以写个判断,要求用户移除即可。
优点是页面会变得清爽简洁,加载的内容会减少。缺点是当量增加时,服务器压力加大。浏览器延迟加长,页面转化率低。
占有率统计,跳转列表,
人工拉页就ok,这样有可能会造成一个单页面的上拉列表带动很多页面上拉的情况(这个正常,不要理解偏差。要把页面容量定得小点,动态绑定两个事件,直接从api里给他连接a.addeventlistener('target',path.join({scrolltop:1,scrolltopleft:1}),axios.action({expressive:true})),此外,你定的时间点很重要,如果早上发现设定好了时间点出问题,很崩溃,就要纠结下自己设定的时间点的问题了。
额,上面的方法应该是两种,但是题主你加上空格,
如果网站只是内容的增删,就每页增加一个n(n取决于内容量),每页移动一个n个dom(例如只有一个或者空的dom页,所以每页在n个dom中移动)即可。没有必要全部都要自己定义布局。如果涉及到的新增和删除这两个操作都有了并且在数据库里有索引的话可以使用z-index实现。提升空间肯定是有的,但可行性并不高。用jquery的话还能实现批量操作布局。
js提取指定网站内容是什么?提取原理是怎样的?
网站优化 • 优采云 发表了文章 • 0 个评论 • 39 次浏览 • 2022-06-05 05:02
js提取指定网站内容一般就是提取整个网站的所有链接。在提取链接的时候主要就是对链接进行分析,找到指定页面的索引号,在一些社交网站和各大网站,一般都会发布自己网站的链接,也就是说只要找到了指定网站的指定页面,那么找到的这个网站的链接就自然而然是网站内容了。提取整个网站和仅提取某些页面是大相径庭的。链接提取原理:1.一个页面有多个链接需要分析出哪个页面代表着网站内容主页的指定页面,再对其里面的链接进行一一对应,打点就可以。
2.网站所有页面所有链接里有唯一的一个,那么此时也可以打个标记,打出该标记的,就可以认为是所有网站内容的主页,再对其进行提取查询即可。3.根据网站提取所需内容的属性,单独查询每个页面的链接,其内容只有指定页面的内容,提取查询即可。4.找到唯一符合需求的页面,也就是在列表页中的搜索框中,输入关键词后,你可以找到搜索内容,根据页面的内容来匹配内容,就可以得到所有网站内容的索引号。
5.从该页面的链接中提取,确定该页面具体是哪个搜索内容的页面,在列表页内搜索关键词后,可以找到该页面的内容,匹配页面内容(指定页面内容),就可以找到所有网站内容的索引号。6.提取完全需要的页面内容后,再根据实际需求进行查询即可。7.利用google的关键词功能,提取出关键词出来,就可以写一个spider进行搜索,一般网站如果内容很多很杂,且每个页面都需要内容,就会分两个号进行爬取,一个搜索关键词,一个搜索内容(即spider),爬取方式可以是每次爬取一个页面,查找后进行二次搜索。
备注:webspider在爬取的时候,会对全站爬取,因此不建议采用第一种方式,目前常用一种方式就是提取单个页面的链接后,再通过打点找到对应的指定页面内容,再提取出来,这样做的好处在于每个网站内容的种类不同,自然查找的内容也会有差异,也不太会造成流量的损失,相对来说,这种方式比较安全。webspider一般是通过谷歌搜索,搜索一些大词或者比较火的词,注意一定是要大词,不能通过一些比较冷的词,小词来搜索。
再根据页面的内容再进行分析,提取出指定网站内容。如果是爬取某些大站的内容,比如百度首页内容,可以先通过谷歌网站快照找到关键词,例如:某个站点上这个关键词,排在前50位的是谁,等,然后就是在页面后面添加关键词,例如:“”,页面抓取后,通过分析每个页面的内容,提取出需要的内容,如果是想对某一个网站页面内容或者整个网站内容进行抓取,那么就要对该网站的信息分析,清楚的知道这个网站的所有页面内容,并在。 查看全部
js提取指定网站内容是什么?提取原理是怎样的?
js提取指定网站内容一般就是提取整个网站的所有链接。在提取链接的时候主要就是对链接进行分析,找到指定页面的索引号,在一些社交网站和各大网站,一般都会发布自己网站的链接,也就是说只要找到了指定网站的指定页面,那么找到的这个网站的链接就自然而然是网站内容了。提取整个网站和仅提取某些页面是大相径庭的。链接提取原理:1.一个页面有多个链接需要分析出哪个页面代表着网站内容主页的指定页面,再对其里面的链接进行一一对应,打点就可以。
2.网站所有页面所有链接里有唯一的一个,那么此时也可以打个标记,打出该标记的,就可以认为是所有网站内容的主页,再对其进行提取查询即可。3.根据网站提取所需内容的属性,单独查询每个页面的链接,其内容只有指定页面的内容,提取查询即可。4.找到唯一符合需求的页面,也就是在列表页中的搜索框中,输入关键词后,你可以找到搜索内容,根据页面的内容来匹配内容,就可以得到所有网站内容的索引号。
5.从该页面的链接中提取,确定该页面具体是哪个搜索内容的页面,在列表页内搜索关键词后,可以找到该页面的内容,匹配页面内容(指定页面内容),就可以找到所有网站内容的索引号。6.提取完全需要的页面内容后,再根据实际需求进行查询即可。7.利用google的关键词功能,提取出关键词出来,就可以写一个spider进行搜索,一般网站如果内容很多很杂,且每个页面都需要内容,就会分两个号进行爬取,一个搜索关键词,一个搜索内容(即spider),爬取方式可以是每次爬取一个页面,查找后进行二次搜索。
备注:webspider在爬取的时候,会对全站爬取,因此不建议采用第一种方式,目前常用一种方式就是提取单个页面的链接后,再通过打点找到对应的指定页面内容,再提取出来,这样做的好处在于每个网站内容的种类不同,自然查找的内容也会有差异,也不太会造成流量的损失,相对来说,这种方式比较安全。webspider一般是通过谷歌搜索,搜索一些大词或者比较火的词,注意一定是要大词,不能通过一些比较冷的词,小词来搜索。
再根据页面的内容再进行分析,提取出指定网站内容。如果是爬取某些大站的内容,比如百度首页内容,可以先通过谷歌网站快照找到关键词,例如:某个站点上这个关键词,排在前50位的是谁,等,然后就是在页面后面添加关键词,例如:“”,页面抓取后,通过分析每个页面的内容,提取出需要的内容,如果是想对某一个网站页面内容或者整个网站内容进行抓取,那么就要对该网站的信息分析,清楚的知道这个网站的所有页面内容,并在。
JavaScript快速提取子域名URL工具
网站优化 • 优采云 发表了文章 • 0 个评论 • 56 次浏览 • 2022-06-04 12:36
0x01 JSFinder介绍
JSFinder 是一种用于从网站上的 JS 文件中快速提取 URL 和子域的工具,提取URL的正则部分使用的是LinkFinder项目。
增加油猴脚本用于在浏览器上访问页面时获取域名与接口,具体可见:
https://github.com/Threezh1/De ... inder
JSFinder获取URL和子域名的方式:
0x02JSFinder使用
python JSFinder.py -u http://xxxx.com
这个命令会爬取这个别页面的所有js链接,并在其中发现url和子域名
python JSFinder.py -u http://www.xxxx.com -d
深入一层页面爬取JS,时间会消耗时间,建议使用-ou和-os来指定保存URL和子域名的文件名。
python JSFinder.py -u http://www.xxxx.com -d -ou mi_url.txt -os mi_subdomain.txt
指定网址:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不用加深度爬取,一页图表。
-c 指定cookie来爬取页面示例:
python JSFinder.py -u http://www.xxxx.com -c "session=xxx"
-ou 指定文件名保存URL链接示例:
python JSFinder.py -u http://www.xxxx.com -ou XX_url.txt
-os 指定文件名保存子域名示例:
python JSFinder.py -u http://www.xxxx.com -os mi_subdomain.txt
0x03JSFinder获取 查看全部
JavaScript快速提取子域名URL工具
0x01 JSFinder介绍
JSFinder 是一种用于从网站上的 JS 文件中快速提取 URL 和子域的工具,提取URL的正则部分使用的是LinkFinder项目。
增加油猴脚本用于在浏览器上访问页面时获取域名与接口,具体可见:
https://github.com/Threezh1/De ... inder
JSFinder获取URL和子域名的方式:
0x02JSFinder使用
python JSFinder.py -u http://xxxx.com
这个命令会爬取这个别页面的所有js链接,并在其中发现url和子域名
python JSFinder.py -u http://www.xxxx.com -d
深入一层页面爬取JS,时间会消耗时间,建议使用-ou和-os来指定保存URL和子域名的文件名。
python JSFinder.py -u http://www.xxxx.com -d -ou mi_url.txt -os mi_subdomain.txt
指定网址:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不用加深度爬取,一页图表。
-c 指定cookie来爬取页面示例:
python JSFinder.py -u http://www.xxxx.com -c "session=xxx"
-ou 指定文件名保存URL链接示例:
python JSFinder.py -u http://www.xxxx.com -ou XX_url.txt
-os 指定文件名保存子域名示例:
python JSFinder.py -u http://www.xxxx.com -os mi_subdomain.txt
0x03JSFinder获取
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 62 次浏览 • 2022-05-29 20:31
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-05-28 00:21
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
JavaScript数组常用方法解析和深层次js数组扁平化
网站优化 • 优采云 发表了文章 • 0 个评论 • 75 次浏览 • 2022-05-26 16:22
JavaScript数组常用方法解析和深层次js数组扁平化前言
数组作为在开发中常用的集合,除了for循环遍历以外,还有很多内置对象的方法,包括map,以及数组筛选元素filter等。
注:文章结尾处附深层次数组扁平化方法操作。
作为引用数据类型的一种,在处理数组Array的时候,我们需要考虑到深拷贝和浅拷贝的情况可以参考以下文章
常用数组操作方法push末尾追加元素
/**<br /> * @param push 将一个或多个元素添加到数组的末尾,返回该数组的新长度<br /> *<br /> * 集合apply和call合并数组<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.push("xiaoming")); // 3<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br />let user1 = ["xiaowang", "xiaoming"];<br />let user2 = ["zhangsan", "lisi"];<br />console.log(Array.prototype.push.apply(user1, user2)); // 4<br />console.log(user1); // ["xiaowang", "xiaoming", "zhangsan", "lisi"]<br /><br />
pop删除数组末尾元素
/**<br /> *<br /> * @param pop 方法从数组中删除最后一个元素,返回值是该元素。<br /> *<br /> * 如果数组是空数组,那么返回的是undefined<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.pop()); // lisi<br />console.log(user); // ["zhangsan"]<br />let empArray = [];<br />console.log(empArray.pop()); // undefined<br /><br />
sort排序
/**<br /> *<br /> * @param sort<br /> *<br /> * 使用原地算法对数组的元素进行排序,并返回数组。<br /> * 默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的<br /> * 由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。<br /> *<br /> * arr.sort([compareFunction])<br /> *<br /> * @param compareFunction<br /> *<br /> * 用来指定按某种顺序进行排列的函数。<br /> * 如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。<br /> *<br /> * 如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。<br /> * 例如 "Banana" 会被排列到 "cherry" 之前。<br /> * 当数字按由小到大排序时,9 出 * 现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。<br /> * 如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:<br /> * 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;<br /> * 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。<br /> * 备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);<br /> * 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。<br /> * compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。<br /> *<br /> * firstEl<br /> * 第一个用于比较的元素。<br /> * secondEl<br /> * 第二个用于比较的元素<br /> *<br /> */<br />/**<br /> *<br /> * 基本用法<br /> *<br /> * */<br /><br />const user = ["zhangsan", "lisi", "xiaoming", "xiaowang"];<br />user.sort();<br />console.log(user); // ["lisi", "xiaoming", "xiaowang", "zhangsan"]<br />const array1 = [1, 30, 4, 21, 100000];<br />array1.sort();<br />console.log(array1); // [1, 100000, 21, 30, 4]<br /><br />/**<br /> *<br /> * 自定义排序方法<br /> *<br /> * */<br />var numbers = [4, 2, 5, 1, 3];<br />let sortFun = function (a, b) {<br /> return a - b;<br />};<br />numbers.sort(sortFun);<br />console.log(numbers); // [1, 2, 3, 4, 5]<br /><br />
shift数组开头添加元素 && unshift数组开头删除元素
/**<br /> *<br /> * @param shift<br /> * 从数组中删除第一个元素,并返回该元素的值,如果删除空数组,返回值是undefined<br /> *<br /> * @param unshift<br /> * 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度<br /> *<br /> * */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.shift()); // zhangsan<br />console.log(user); // ["lisi"]<br />let empArray = [];<br />console.log(empArray.shift()); // undefined<br />let user1 = ["xiaoming", "xiaowang"];<br />console.log(user1.unshift("xiaoming1", "xiaowang1")); // 4<br />console.log(user1); // ["xiaoming1", "xiaowang1", "xiaoming", "xiaowang"]<br /><br />
数组合并concat
<br />/**<br /> *<br /> * @param concat<br /> *<br /> * 方法用于合并两个或多个数组。返回值是新数组,原数组不会发生更改<br /> *<br /> * 注:数组合并是浅拷贝<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />let user1 = [["xiaowang"], { name: "xiaoming" }];<br />console.log(user.concat(user1)); // ["zhangsan","lisi",["xiaowang"],{name: "xiaoming"}]<br />console.log(user); // ["zhangsan", "lisi"]<br /><br />
indexOf查找元素 && includes查找元素是否存在
/**<br /> *<br /> * @param indexOf<br /> *<br /> * 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1,<br /> * 常用于判断数组是否存在某个元素<br /> *<br /> * @param includes<br /> *<br /> * 判断一个数组是否包含一个指定的值,返回值是布尔值 true 或者 false<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.indexOf("lisi")); // 1<br />console.log(user.indexOf("xiaoming")); // -1<br />let user1 = ["zhangsan", ["xiaowang"], { name: "xiaoming" }];<br />console.log(user1.includes("zhangsan")); // true<br />console.log(user1.includes(["xiaowang"])); // false<br />console.log(user1.includes({ name: "xiaoming" })); // false<br /><br />
reverse反转数组
/**<br /> *<br /> * @param reverse<br /> *<br /> * 反转数组元素,将原有数组倒叙显示,会改变元素的元素位置<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.reverse()); // ["xiaoming", "lisi", "zhangsan"]<br />console.log(user); // ["xiaoming", "lisi", "zhangsan"]<br />let user1 = ["zhangsan", ["xiaowang", "lisi"], { name: "xiaoming" }];<br />console.log(user1.reverse()); // [{name: "xiaoming"},["xiaowang", "lisi"],"zhangsan"]<br /><br />
数组切割成字符串join
/**<br /> *<br /> * @param join<br /> *<br /> * 根据传入的参数字符串,对数组进行切割,返回值是使用参数拼接元素的字符串<br /> * 如果数组只有一个元素,则不使用分割符号<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.join(" ")); // zhangsan lisi xiaoming<br />console.log(user.join("")); // zhangsanlisixiaoming<br />console.log(user.join(",")); // zhangsan,lisi,xiaoming<br />console.log(user.join({ a: 1 })); // zhangsan[object Object]lisi[object Object]xiaoming<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br /><br />
slice操作数组,替换,删除,新增
slice使用的范围较广,不同的参数可以实现对数组的删除,新增和替换等,使用的时候需要注意参数的具体使用方法
/**<br /> *<br /> * @param slice<br /> *<br /> * 返回一个新的数组对象,<br /> * 这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。<br /> *<br /> * @param begin<br /> * 提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。<br /> * 如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。<br /> * 如果省略 begin,则 slice 从索引 0 开始。<br /> * 如果 begin 超出原数组的索引范围,则会返回空数组<br /> *<br /> * @param end<br /> *<br /> * 提取终止处的索引(从 0 开始),在该索引处结束提取原数组元素。<br /> * slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。<br /> * slice(1,4) 会提取原数组中从第二个元素开始一直到第四个元素的所有元素 (索引为 1, 2, 3的元素)。<br /> * 如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。<br /> * slice(-2,-1) 表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。<br /> * 如果 end 被省略,则 slice 会一直提取到原数组末尾。如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。<br /> *<br /> */<br />const animals = ["ant", "bison", "camel", "duck", "elephant"];<br />console.log(animals.slice(2)); // Array ["camel", "duck", "elephant"]<br />console.log(animals.slice(2, 4)); // Array ["camel", "duck"]<br />console.log(animals.slice(1, 5)); // Array ["bison", "camel", "duck", "elephant"]<br />console.log(animals.slice(-2)); // Array ["duck", "elephant"]<br />console.log(animals.slice(2, -1)); // Array ["camel", "duck"]<br />console.log(animals.slice()); // Array ["ant", "bison", "camel", "duck", "elephant"]<br />/**<br /> *<br /> * @param splice(start[, deleteCount[, item1[, item2[, ...]]]])<br /> *<br /> * 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组<br /> *<br /> * 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。<br /> *<br /> * @param start<br /> *<br /> * 指定修改的开始位置,默认从下标0开始。<br /> * 如果超出了数组的长度,则从数组末尾开始添加元素;<br /> * 如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);<br /> * 如果负数的绝对值大于数组的长度,则表示开始位置为第0位。<br /> *<br /> * @param deleteCount<br /> *<br /> * 整数,表示要移除的数组元素的个数。<br /> * 如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。<br /> * 如果 deleteCount 被省略了,<br /> * 或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),<br /> * 那么start之后数组的所有元素都会被删除。<br /> *<br /> * 如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。<br /> * @param item1, item2, ...<br /> *<br /> * 要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素<br /> *<br /> */<br />const months = ["Jan", "March", "April", "June"];<br />months.splice(1, 0, "Feb"); // 下表为1,插入一个元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "June"]<br />months.splice(4, 1, "May"); // 替换下标为4的元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "May"]<br />let del = months.splice(1, 1); // 删除<br />console.log(del); // ["Feb"]<br />console.log(months); // ["Jan", "April", "May"]<br /><br />
every校验数组所有元素
/**<br /> *<br /> * @param every<br /> * 测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回值是布尔值 true or false<br /> * 备注:若收到一个空数组,此方法在任何情况下都会返回 true。<br /> *<br /> * arr.every(callback(element[, index[, array]])[, thisArg])<br /> * callback<br /> * 用来测试每个元素的函数,它可以接收三个参数:<br /> *<br /> * @param element 用于测试的当前值。<br /> * @param index可选 用于测试的当前值的索引。<br /> * @param array可选 调用 every 的当前数组。<br /> *<br /> * every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 false 的元素。<br /> * 如果发现了一个这样的元素,every 方法将会立即返回 false。<br /> * 否则,callback 为每一个元素返回 true,every 就会返回 true。<br /> *<br /> * callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从未被赋值的索引调用。<br /> * callback 在被调用时可传入三个参数:元素值,元素的索引,原数组。<br /> * 如果为 every 提供一个 thisArg 参数,则该参数为调用 callback 时的 this 值。<br /> * 如果省略该参数,则 callback 被调用时的 this 值,在非严格模式下为全局对象,在严格模式下传入 undefined。<br /> *<br /> *<br /> * every 不会改变原数组。<br /> * every 遍历的元素范围在第一次调用 callback 之前就已确定了。<br /> * 在调用 every 之后添加到数组中的元素不会被 callback 访问到。<br /> * 如果数组中存在的元素被更改,则他们传入 callback 的值是 every 访问到他们那一刻的值。<br /> * 那些被删除的元素或从来未被赋值的元素将不会被访问到。<br /> *<br /> * */<br />const isBelowThreshold = (currentValue) => currentValue element % 2 === 0; //确认偶数<br />console.log(array.some(even)); // true;<br /><br />
深层次递归数组flat
<br />/**<br /> * @param flat 按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。<br /> *<br /> * var newArray = arr.flat([depth])<br /> * @depth 指定要提取嵌套数组的结构深度,默认值为 1。<br /> * */<br />let arr1 = [1, 2, [3, 4]];<br />console.log(arr1.flat()); // [1, 2, 3, 4]<br />let arr2 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]]<br />let arr3 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr3.flat(2)); // [1, 2, 3, 4, 5, 6]<br />//使用 Infinity,可展开任意深度的嵌套数组<br />let arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];<br />console.log(arr4.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]<br />let objArray = [{ name: "zhangsan", children: ["张三"] }];<br />console.log(objArray.flat(Infinity)); // [{ name: "zhangsan", children: ["张三"] }]<br /><br />
map遍历数组
/**<br /> * @param map<br /> *<br /> * 创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成<br /> *<br /> * */<br />const array1 = [1, 4, 9, 16];<br />const map1 = array1.map((x) => x * 2);<br />console.log(map1); // [2, 8, 18, 32]<br /><br />
reduce和filter
reduce和filter的基本操作方法在之前的文章有提到过,这里不做复述
文章地址:JavaScript 数组方法filter和reduce
数组操作示例:数组对象根据属性整理数组
/**<br /> * 按照数组对象某个属性整理数据<br /> *<br /> * */<br />let user1 = [<br /> { name: "zhangsan", age: 21 },<br /> { name: "lisi", age: 20 },<br /> { name: "xiaoming", age: 20 },<br />];<br />function groupBy(objectArray, property) {<br /> return objectArray.reduce(function (acc, obj) {<br /> let key = obj[property];<br /> if (!acc[key]) {<br /> acc[key] = [];<br /> }<br /> acc[key].push(obj);<br /> return acc;<br /> }, {});<br />}<br />let ageList = groupBy(user1, "age");<br />console.log(ageList); // {[{name: "lisi", age: 20},{name: "xiaoming", age: 20}],[{name: "zhangsan", age: 21}]}<br /><br /><br />
数组扁平化-深层次
function flatten(array) {<br /> var flattend = [];<br /> (function flat(array) {<br /> array.forEach(function (el) {<br /> for (let i in el) {<br /> if (Object.prototype.toString.call(el[i]) === "[object Array]")<br /> flat(el[i]);<br /> }<br /> flattend.push(el);<br /> });<br /> })(array);<br /> return flattend;<br />}<br />let user2 = [<br /> {<br /> name: "zhangsan",<br /> age: 20,<br /> child: [{ name: "xiaoming" }],<br /> child1: [{ name: "xiaowang" }],<br /> },<br />];<br />let flattenArray = flatten(user2);<br />console.log(flattenArray);<br /><br />
结尾
以上就是JavaScript中数组较为常用的方法,其他没有提及的方法,需要的同学可以查阅相关文章,或者留言,后续的文章整理然后作为补充。
源码地址 查看全部
JavaScript数组常用方法解析和深层次js数组扁平化
JavaScript数组常用方法解析和深层次js数组扁平化前言
数组作为在开发中常用的集合,除了for循环遍历以外,还有很多内置对象的方法,包括map,以及数组筛选元素filter等。
注:文章结尾处附深层次数组扁平化方法操作。
作为引用数据类型的一种,在处理数组Array的时候,我们需要考虑到深拷贝和浅拷贝的情况可以参考以下文章
常用数组操作方法push末尾追加元素
/**<br /> * @param push 将一个或多个元素添加到数组的末尾,返回该数组的新长度<br /> *<br /> * 集合apply和call合并数组<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.push("xiaoming")); // 3<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br />let user1 = ["xiaowang", "xiaoming"];<br />let user2 = ["zhangsan", "lisi"];<br />console.log(Array.prototype.push.apply(user1, user2)); // 4<br />console.log(user1); // ["xiaowang", "xiaoming", "zhangsan", "lisi"]<br /><br />
pop删除数组末尾元素
/**<br /> *<br /> * @param pop 方法从数组中删除最后一个元素,返回值是该元素。<br /> *<br /> * 如果数组是空数组,那么返回的是undefined<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.pop()); // lisi<br />console.log(user); // ["zhangsan"]<br />let empArray = [];<br />console.log(empArray.pop()); // undefined<br /><br />
sort排序
/**<br /> *<br /> * @param sort<br /> *<br /> * 使用原地算法对数组的元素进行排序,并返回数组。<br /> * 默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的<br /> * 由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。<br /> *<br /> * arr.sort([compareFunction])<br /> *<br /> * @param compareFunction<br /> *<br /> * 用来指定按某种顺序进行排列的函数。<br /> * 如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。<br /> *<br /> * 如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。<br /> * 例如 "Banana" 会被排列到 "cherry" 之前。<br /> * 当数字按由小到大排序时,9 出 * 现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。<br /> * 如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:<br /> * 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;<br /> * 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。<br /> * 备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);<br /> * 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。<br /> * compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。<br /> *<br /> * firstEl<br /> * 第一个用于比较的元素。<br /> * secondEl<br /> * 第二个用于比较的元素<br /> *<br /> */<br />/**<br /> *<br /> * 基本用法<br /> *<br /> * */<br /><br />const user = ["zhangsan", "lisi", "xiaoming", "xiaowang"];<br />user.sort();<br />console.log(user); // ["lisi", "xiaoming", "xiaowang", "zhangsan"]<br />const array1 = [1, 30, 4, 21, 100000];<br />array1.sort();<br />console.log(array1); // [1, 100000, 21, 30, 4]<br /><br />/**<br /> *<br /> * 自定义排序方法<br /> *<br /> * */<br />var numbers = [4, 2, 5, 1, 3];<br />let sortFun = function (a, b) {<br /> return a - b;<br />};<br />numbers.sort(sortFun);<br />console.log(numbers); // [1, 2, 3, 4, 5]<br /><br />
shift数组开头添加元素 && unshift数组开头删除元素
/**<br /> *<br /> * @param shift<br /> * 从数组中删除第一个元素,并返回该元素的值,如果删除空数组,返回值是undefined<br /> *<br /> * @param unshift<br /> * 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度<br /> *<br /> * */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.shift()); // zhangsan<br />console.log(user); // ["lisi"]<br />let empArray = [];<br />console.log(empArray.shift()); // undefined<br />let user1 = ["xiaoming", "xiaowang"];<br />console.log(user1.unshift("xiaoming1", "xiaowang1")); // 4<br />console.log(user1); // ["xiaoming1", "xiaowang1", "xiaoming", "xiaowang"]<br /><br />
数组合并concat
<br />/**<br /> *<br /> * @param concat<br /> *<br /> * 方法用于合并两个或多个数组。返回值是新数组,原数组不会发生更改<br /> *<br /> * 注:数组合并是浅拷贝<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />let user1 = [["xiaowang"], { name: "xiaoming" }];<br />console.log(user.concat(user1)); // ["zhangsan","lisi",["xiaowang"],{name: "xiaoming"}]<br />console.log(user); // ["zhangsan", "lisi"]<br /><br />
indexOf查找元素 && includes查找元素是否存在
/**<br /> *<br /> * @param indexOf<br /> *<br /> * 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1,<br /> * 常用于判断数组是否存在某个元素<br /> *<br /> * @param includes<br /> *<br /> * 判断一个数组是否包含一个指定的值,返回值是布尔值 true 或者 false<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.indexOf("lisi")); // 1<br />console.log(user.indexOf("xiaoming")); // -1<br />let user1 = ["zhangsan", ["xiaowang"], { name: "xiaoming" }];<br />console.log(user1.includes("zhangsan")); // true<br />console.log(user1.includes(["xiaowang"])); // false<br />console.log(user1.includes({ name: "xiaoming" })); // false<br /><br />
reverse反转数组
/**<br /> *<br /> * @param reverse<br /> *<br /> * 反转数组元素,将原有数组倒叙显示,会改变元素的元素位置<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.reverse()); // ["xiaoming", "lisi", "zhangsan"]<br />console.log(user); // ["xiaoming", "lisi", "zhangsan"]<br />let user1 = ["zhangsan", ["xiaowang", "lisi"], { name: "xiaoming" }];<br />console.log(user1.reverse()); // [{name: "xiaoming"},["xiaowang", "lisi"],"zhangsan"]<br /><br />
数组切割成字符串join
/**<br /> *<br /> * @param join<br /> *<br /> * 根据传入的参数字符串,对数组进行切割,返回值是使用参数拼接元素的字符串<br /> * 如果数组只有一个元素,则不使用分割符号<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.join(" ")); // zhangsan lisi xiaoming<br />console.log(user.join("")); // zhangsanlisixiaoming<br />console.log(user.join(",")); // zhangsan,lisi,xiaoming<br />console.log(user.join({ a: 1 })); // zhangsan[object Object]lisi[object Object]xiaoming<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br /><br />
slice操作数组,替换,删除,新增
slice使用的范围较广,不同的参数可以实现对数组的删除,新增和替换等,使用的时候需要注意参数的具体使用方法
/**<br /> *<br /> * @param slice<br /> *<br /> * 返回一个新的数组对象,<br /> * 这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。<br /> *<br /> * @param begin<br /> * 提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。<br /> * 如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。<br /> * 如果省略 begin,则 slice 从索引 0 开始。<br /> * 如果 begin 超出原数组的索引范围,则会返回空数组<br /> *<br /> * @param end<br /> *<br /> * 提取终止处的索引(从 0 开始),在该索引处结束提取原数组元素。<br /> * slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。<br /> * slice(1,4) 会提取原数组中从第二个元素开始一直到第四个元素的所有元素 (索引为 1, 2, 3的元素)。<br /> * 如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。<br /> * slice(-2,-1) 表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。<br /> * 如果 end 被省略,则 slice 会一直提取到原数组末尾。如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。<br /> *<br /> */<br />const animals = ["ant", "bison", "camel", "duck", "elephant"];<br />console.log(animals.slice(2)); // Array ["camel", "duck", "elephant"]<br />console.log(animals.slice(2, 4)); // Array ["camel", "duck"]<br />console.log(animals.slice(1, 5)); // Array ["bison", "camel", "duck", "elephant"]<br />console.log(animals.slice(-2)); // Array ["duck", "elephant"]<br />console.log(animals.slice(2, -1)); // Array ["camel", "duck"]<br />console.log(animals.slice()); // Array ["ant", "bison", "camel", "duck", "elephant"]<br />/**<br /> *<br /> * @param splice(start[, deleteCount[, item1[, item2[, ...]]]])<br /> *<br /> * 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组<br /> *<br /> * 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。<br /> *<br /> * @param start<br /> *<br /> * 指定修改的开始位置,默认从下标0开始。<br /> * 如果超出了数组的长度,则从数组末尾开始添加元素;<br /> * 如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);<br /> * 如果负数的绝对值大于数组的长度,则表示开始位置为第0位。<br /> *<br /> * @param deleteCount<br /> *<br /> * 整数,表示要移除的数组元素的个数。<br /> * 如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。<br /> * 如果 deleteCount 被省略了,<br /> * 或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),<br /> * 那么start之后数组的所有元素都会被删除。<br /> *<br /> * 如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。<br /> * @param item1, item2, ...<br /> *<br /> * 要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素<br /> *<br /> */<br />const months = ["Jan", "March", "April", "June"];<br />months.splice(1, 0, "Feb"); // 下表为1,插入一个元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "June"]<br />months.splice(4, 1, "May"); // 替换下标为4的元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "May"]<br />let del = months.splice(1, 1); // 删除<br />console.log(del); // ["Feb"]<br />console.log(months); // ["Jan", "April", "May"]<br /><br />
every校验数组所有元素
/**<br /> *<br /> * @param every<br /> * 测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回值是布尔值 true or false<br /> * 备注:若收到一个空数组,此方法在任何情况下都会返回 true。<br /> *<br /> * arr.every(callback(element[, index[, array]])[, thisArg])<br /> * callback<br /> * 用来测试每个元素的函数,它可以接收三个参数:<br /> *<br /> * @param element 用于测试的当前值。<br /> * @param index可选 用于测试的当前值的索引。<br /> * @param array可选 调用 every 的当前数组。<br /> *<br /> * every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 false 的元素。<br /> * 如果发现了一个这样的元素,every 方法将会立即返回 false。<br /> * 否则,callback 为每一个元素返回 true,every 就会返回 true。<br /> *<br /> * callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从未被赋值的索引调用。<br /> * callback 在被调用时可传入三个参数:元素值,元素的索引,原数组。<br /> * 如果为 every 提供一个 thisArg 参数,则该参数为调用 callback 时的 this 值。<br /> * 如果省略该参数,则 callback 被调用时的 this 值,在非严格模式下为全局对象,在严格模式下传入 undefined。<br /> *<br /> *<br /> * every 不会改变原数组。<br /> * every 遍历的元素范围在第一次调用 callback 之前就已确定了。<br /> * 在调用 every 之后添加到数组中的元素不会被 callback 访问到。<br /> * 如果数组中存在的元素被更改,则他们传入 callback 的值是 every 访问到他们那一刻的值。<br /> * 那些被删除的元素或从来未被赋值的元素将不会被访问到。<br /> *<br /> * */<br />const isBelowThreshold = (currentValue) => currentValue element % 2 === 0; //确认偶数<br />console.log(array.some(even)); // true;<br /><br />
深层次递归数组flat
<br />/**<br /> * @param flat 按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。<br /> *<br /> * var newArray = arr.flat([depth])<br /> * @depth 指定要提取嵌套数组的结构深度,默认值为 1。<br /> * */<br />let arr1 = [1, 2, [3, 4]];<br />console.log(arr1.flat()); // [1, 2, 3, 4]<br />let arr2 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]]<br />let arr3 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr3.flat(2)); // [1, 2, 3, 4, 5, 6]<br />//使用 Infinity,可展开任意深度的嵌套数组<br />let arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];<br />console.log(arr4.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]<br />let objArray = [{ name: "zhangsan", children: ["张三"] }];<br />console.log(objArray.flat(Infinity)); // [{ name: "zhangsan", children: ["张三"] }]<br /><br />
map遍历数组
/**<br /> * @param map<br /> *<br /> * 创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成<br /> *<br /> * */<br />const array1 = [1, 4, 9, 16];<br />const map1 = array1.map((x) => x * 2);<br />console.log(map1); // [2, 8, 18, 32]<br /><br />
reduce和filter
reduce和filter的基本操作方法在之前的文章有提到过,这里不做复述
文章地址:JavaScript 数组方法filter和reduce
数组操作示例:数组对象根据属性整理数组
/**<br /> * 按照数组对象某个属性整理数据<br /> *<br /> * */<br />let user1 = [<br /> { name: "zhangsan", age: 21 },<br /> { name: "lisi", age: 20 },<br /> { name: "xiaoming", age: 20 },<br />];<br />function groupBy(objectArray, property) {<br /> return objectArray.reduce(function (acc, obj) {<br /> let key = obj[property];<br /> if (!acc[key]) {<br /> acc[key] = [];<br /> }<br /> acc[key].push(obj);<br /> return acc;<br /> }, {});<br />}<br />let ageList = groupBy(user1, "age");<br />console.log(ageList); // {[{name: "lisi", age: 20},{name: "xiaoming", age: 20}],[{name: "zhangsan", age: 21}]}<br /><br /><br />
数组扁平化-深层次
function flatten(array) {<br /> var flattend = [];<br /> (function flat(array) {<br /> array.forEach(function (el) {<br /> for (let i in el) {<br /> if (Object.prototype.toString.call(el[i]) === "[object Array]")<br /> flat(el[i]);<br /> }<br /> flattend.push(el);<br /> });<br /> })(array);<br /> return flattend;<br />}<br />let user2 = [<br /> {<br /> name: "zhangsan",<br /> age: 20,<br /> child: [{ name: "xiaoming" }],<br /> child1: [{ name: "xiaowang" }],<br /> },<br />];<br />let flattenArray = flatten(user2);<br />console.log(flattenArray);<br /><br />
结尾
以上就是JavaScript中数组较为常用的方法,其他没有提及的方法,需要的同学可以查阅相关文章,或者留言,后续的文章整理然后作为补充。
源码地址
js提取指定网站内容或页面内容为字符串js获取
网站优化 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-05-15 03:04
js提取指定网站内容或页面内容为字符串js获取包含某个或多个script标签的文件:使用javascriptscript-loaderscript-loader也可以通过javascriptscript-regex具体详见:
参考thegooglestyleguide
dom提取?不是很建议这么做,毕竟浏览器只支持预览。如果这么做用到了javascriptscript的话,那岂不是只是拼拼符号,不支持预览。按javascript的原理,想要提取文本,首先要声明一个class的变量,再创建一个script标签,随之进行编写自己想要的样式。根据自己的描述,猜测你应该是想生成的每个script标签中的某些字符串想要进行提取。
上面说的对,另外有个更优雅的方式:利用javascript语言特性,
javascript特有的语法,特殊的方法,无论是传统提取还是使用ajax提取,都是有一定优势的。先看一下传统提取方法传统的提取方法对于dom提取,目标是什么呢?script标签(不包括frame),通过中间嵌套一个dom,然后进行提取。这样一个标签中也许有多个内容,在这种情况下提取其中一个内容即可。
那么问题来了,因为传统提取方法的架构复杂,通常需要对dom进行初步分析,进而提取元素。其次因为中间嵌套多个dom,导致dom分析的过程异常麻烦。至于提取多个元素,对于嵌套中间的dom对象,dom还是同一个dom对象吗?是的,是一个dom对象。但是,同一个dom对象并不代表是唯一的。这是因为即使是script标签,它每个内容都可以是不同的,我们需要把它提取为同一个dom对象,然后组合提取多个。
ajax提取此方法对dom提取比传统方法优势极大,就是对dom提取起步简单许多。目标不是script标签,仅仅对dom进行提取即可。对于dom提取有一个缺点,那就是如果不用相应的中间层,单独写一个script中间标签,效率极低,整个工作量极大。而使用ajax提取,可以获取到dom,然后用javascript中间层提取提取多个。
而采用传统提取,必须是直接将dom提取出来放进dom对象进行组合操作,才能组合提取多个,然后整个工作量极大。基于以上,javascript提取相比传统提取优势非常明显。而且也能为后续优化提供基础。 查看全部
js提取指定网站内容或页面内容为字符串js获取
js提取指定网站内容或页面内容为字符串js获取包含某个或多个script标签的文件:使用javascriptscript-loaderscript-loader也可以通过javascriptscript-regex具体详见:
参考thegooglestyleguide
dom提取?不是很建议这么做,毕竟浏览器只支持预览。如果这么做用到了javascriptscript的话,那岂不是只是拼拼符号,不支持预览。按javascript的原理,想要提取文本,首先要声明一个class的变量,再创建一个script标签,随之进行编写自己想要的样式。根据自己的描述,猜测你应该是想生成的每个script标签中的某些字符串想要进行提取。
上面说的对,另外有个更优雅的方式:利用javascript语言特性,
javascript特有的语法,特殊的方法,无论是传统提取还是使用ajax提取,都是有一定优势的。先看一下传统提取方法传统的提取方法对于dom提取,目标是什么呢?script标签(不包括frame),通过中间嵌套一个dom,然后进行提取。这样一个标签中也许有多个内容,在这种情况下提取其中一个内容即可。
那么问题来了,因为传统提取方法的架构复杂,通常需要对dom进行初步分析,进而提取元素。其次因为中间嵌套多个dom,导致dom分析的过程异常麻烦。至于提取多个元素,对于嵌套中间的dom对象,dom还是同一个dom对象吗?是的,是一个dom对象。但是,同一个dom对象并不代表是唯一的。这是因为即使是script标签,它每个内容都可以是不同的,我们需要把它提取为同一个dom对象,然后组合提取多个。
ajax提取此方法对dom提取比传统方法优势极大,就是对dom提取起步简单许多。目标不是script标签,仅仅对dom进行提取即可。对于dom提取有一个缺点,那就是如果不用相应的中间层,单独写一个script中间标签,效率极低,整个工作量极大。而使用ajax提取,可以获取到dom,然后用javascript中间层提取提取多个。
而采用传统提取,必须是直接将dom提取出来放进dom对象进行组合操作,才能组合提取多个,然后整个工作量极大。基于以上,javascript提取相比传统提取优势非常明显。而且也能为后续优化提供基础。
js提取指定网站内容 关于Python的劝学指南
网站优化 • 优采云 发表了文章 • 0 个评论 • 47 次浏览 • 2022-05-01 15:49
关于Python的劝学指南
最近和几位朋友聊天,无意中谈到了关于Python的话题,这几位朋友之中有做财务的、有做投资的、也有项目负责人还有一些法务的朋友,他们所从事的行业与计算机没有太多关联,但是对Python所表现出来的热情,着实让我感到意外。他们选择学习Python的原因大致有两点:第一,认为Python很有用(有需求);第二,认为Python很简单(成本低,主要指时间成本)。那么,作为非计算机行业的从业人员,比如法律或者财税等其他非计算机行业的职业者,是否有学习这项技能的必要?这是我们今天要分享的话题。有言在先,分享这个话题,并不代表我本人的计算机水平达到了某种程度,只是作为一个业余学习Python的“过来人”,尝试对同样准备学习他的朋友们提供一些参考。对于专业选手来讲,本文的一些观点或许并不适用。
Python很有用?编程之路大致有两种途径,一种是先系统地学习编程语言,然后用语言解决遇到的问题;另一种是先有了应用场景需求,然后拆分问题寻找编程的实现方法。第一种属于被动接受的学习方式,也是传统教育普遍采用的培养方式,优点是基础往往比较牢靠,对知识也有全面的认知,缺点是学习的时间比较久,前期看不到成效并且不容易坚持下来。另一种属于主动探索的学习方式,通常是脱离学校教育后的主要学习方式,优点是目标和需求比较明确,能切实地解决问题,缺点也比较明显,没有系统化的指导和培养,往往需要自己寻找解决各种问题的方案,会走一些弯路,需要一点百折不挠的劲头才行。前者,适用于几乎所有的语言学习,后者适用于简单的语言学习,比如Python,也适用于从一门语言向另一门语言转变的学习,比如从C++转学Python。我是接触计算机比较早的一批人,起先学习的编程语言是C++,但是在取得该语言等级认证后的很长一段时间里,我并没有做出过任何一个实用的程序项目,因为如果不从事计算机行业,这些编程语言几乎没有任何应用场景,但Python算是个例外。我使用Python的第一个应用场景是毕业后的租房。当年的豆瓣网还是个很纯粹的平台,上海许多优质的房屋直租信息常常会在这个平台发布,对于有租房需求又没时间泡在平台上的我来讲,通过程序检索指定区域内的租房信息并将信息实时反馈给我,无疑是一个不错的方案,于是我向计算机专业的朋友请教了解决方案,他们向我推荐了Python(相比之下,这个场景用包括C语言在内的其他语言都很难实现),在见识到它强大的网络爬虫功能后,我开启了自己的Python之路。
律师的职业中,Python的应用场景主要围绕着文本文档处理和信息筛查展开,具体可以分为这样几类:(1)文本信息的筛查,尤其是对批量文本进行深度检索。比如,某年在某省高院协调处理地产纠纷诉讼事务的时候,我们对于解除房地产合作协议的诉讼案件收费标准与当地法院有了不同的看法,当地法院倾向于按照合同总价款的比例收取,而我们倾向于按照申请金额的比例收取,两个收费的标准相差了近600多万。为了佐证我们的观点,我们调取了北京、上海、广州等几个重要城市的近500余份裁判文书,并从中提取出诉讼费的裁判数据,形成了一份关于诉讼费收取的专项报告,其中对文书筛选的工作便是通过编程来完成的。(2)网站关键信息检索。比如,在一些官方网站检索功能受限的情况下,通过遍历网页的方式可以较准确地检索到官方公布的数据和文书(此处,忍住吐嘈某度在信息检索方面的商业化运作,以及某些行政类网站信息维护的质量,暂略去1000字)。又比如,在公报案例库还不健全的时候,通过自定义编程建立起自己的数据中心。(3)资料的收集、归纳和整理。比如,某年在研究某集团公司历年来的1100余件重大诉讼案件的过程中,提取历年来的案件信息,汇总整理报告的部分,也是通过编程辅助完成的。
又比如,在企业并购的业务中,需要对某上市公司历年公报信息进行收集,正常情况下历年公布的信息需要逐一下载,这项工作同样也可以通过程序自动处理。(4)格式化文本及文档。包括PDF及OFFICE格式文档操作,格式转换,信息提取与变更等,通常可以很方便地实现多任务的串联操作。对于想要接触Python的朋友,建议大家认真地了解一下编程与自身应用场景需求之间的匹配问题,如果有好的软件可以方便地实现这些应用场景,其实并不需要自行编程处理。比如,一些课程机构宣扬的用Python操作Excel,就我个人的体验来看,其中很多应用场景并不如使用Office办公软件方便,倒是财会的朋友常用的VBA功能可以尝试使用Python作为替换。技术发展至今,编程所能够解决的问题仍然局限于批量的、规律的以及重复性的一些工作,或者把一些分散的独立任务串联到一起,这是编程所能实现的基本任务。如果想要应对那些少量的、不规律的以及一次性的工作,启用编程并不是一个好的选择。因为少量的工作可以径直做完,并不需要通过编程处理(技术化解决的优势并不明显);而不规律的工作场景,由于所需要的个性化细节过多,一般的编程技术难以实现或者技术尚不能实现;对于一次性的工作需要考虑编程花费的时间成本与收获之间的平衡,花费过多而收获却少反倒不如直接处理来的快,这是在实用性能之外需要考虑的成本因素。
当然,有一部分朋友需要用编程解决一些专业性的计算和模拟测试等问题,这不在本文的讨论之列,这类需求应当归入专业需求者的行列,不适用于业余需求者。Python很容易?片面地强调Python学习起来很容易是不对的,事实上,容易与困难始终是相对的。我们可以说Python语言相对于其他语言来讲容易的多,因为有良好的扩展性能,许多应用场景可以由封装完备的库(可以理解为系统安装不同的操作软件,只不过这是非视窗化的软件)来实现,因此Python的程序代码比其他语言要简洁许多。早期最流行的一份学习资料是《简明Python教程》的小册子(原作Swaroop,C.H由沈洁元翻译,适用于python2.0版本),整个文档仅有70多页。支持3.0的版本也不需要购买教程,官方已经提供了最权威的版本(详见),这部分内容用一天的时间学习已经足够,哪怕没有编程经验的新手要学完整个课程也不会超过3天时间,这是该语言的特点,也是该语言“很容易”的重要依据。但是仅凭学完这些,你仍不能解决实际中的任何应用问题,因为需要继续了解和掌握的辅助知识还有很多,主要有两大方面:一方面你需要补充学习支持你需求的各类库,比如支持文本处理的Xlings、PyPDF2、python-docx等库,支持数据分析功能的Numpy、Pandas、Matplatlib等库,每一个库所能支持的功能不同,对操作系统的依赖也不相同,这时候我们往往需要阅读这些库的技术文档并做好试错的心理准备。
另一方面,你还需要补充学习一些其他计算机语言的相关知识,比如掌握基本的Linux或CMD操作命令,对于文字处理需求较多的朋友尤其需要掌握正则表达式(类似于Office中的通配符)的使用方法,对爬虫有需求的朋友仍需要学习网络开发语言或者相关技术(包括HTML、CSS、PHP、JS、JQ、AJAX中的一种或者多种组合)。几乎每个应用场景都需要你了解与这项功能相关联的知识,如果前期你对计算机的熟悉程度比较低的话,这里的知识缺失仍然会使你寸步难行。除此以外,你需要付出的额外成本还包括修正各种出其不意的报错,包括:(1)兼容性错误。比如一些库支持Mac系统并不支持Win系统,你需要在系统与库之间做出新的取舍,再比如系统升级而库未升级导致的新问题,软件冲突问题等。(2)程序本身的错误。有些错误是语言书写不规范导致的,这在学习初期尤其严重,比如以往用于2.0的函数或语法被用在了3.0的程序中,比如常见的数据类型、标点、空格以及由于多字、少字、错字引发的各类程序报错,以及对函数调用过程中的出现的规则错误,等等使你妨不胜妨。(3)网络错误或网络受限。在需要网络的各类编程活动中,通常会遇到网络的问题,比如受网络限制官网指定的Brew的安装方法在很多地区并不能操作成功,需要将官方源调整为民间备用源等。
以上是编程会遇到的普遍问题,那么接下来的技术局限是因人而异的另一重困难。几乎每个人都会经历这一过程,那种苦苦思索仍不得其解的感受,可能会使你兴奋,也可能会使你崩溃,你需要有这方面的心理准备。比如冒泡排序法作为经典的排序算法,几乎是每本程序书中都讲过的内容,有的人总也想不明白,也有些人知道了其中的原理,但就是写不出能够运行的程式。我最近曾尝试编写一个查询汉字五笔编码的工具时,尝试了许多方法也找寻了许多教程,但是仍然没有办法得到指定网页反馈来的结果。对于这种经历,解决的途径无非是两种:一是放弃;二是继续想办法攻克(多数人会选择前一项,这与我们处理工作问题时的遭遇相类似)。前者的一次放弃很可能只是下一次放弃的开端,直到你放弃整个语言。而后者意味着你可能需要投入更多的时间,这种投入可能会成功也可能未必会有收获。
每一个解决问题的人,总会遇到更多的问题,这是解题人的宿命。总的来说,Python在工作中的应用场景毋庸置疑,它几乎包含了所有的需要用到电脑工作的行业,而且未来的应用也很可观。当然,我们并不需要过分地追赶这门语言,毕竟要真正掌握这门语言其实并不算太简单,考虑到我们的应用场景和应用频率,对于已经有主要职业的从业者们来讲,学习过程中的所有困难都将折算成为时间成本,而你付出的成本越大这项技能的性价比就越低,这是每个尝试业余了解这项技能的人需要认真考虑的问题。不过,如果你把它当作业余爱好,那就另当别论了,毕竟编程也是一件和打王者荣耀一样很好玩的事。尤其,提出问题、分析问题、解决问题会伴随着整个编程过程,而这个过程会让我们习惯于拆解各项需求,思考实现的步骤,并为实现需求寻找解决方案,最后达到一步步实现需求的效果,这本身就是一种很好的思维训练。以上是本期Til与您分享的话题。感谢您的关注和支持。
查看全部
js提取指定网站内容 关于Python的劝学指南
关于Python的劝学指南
最近和几位朋友聊天,无意中谈到了关于Python的话题,这几位朋友之中有做财务的、有做投资的、也有项目负责人还有一些法务的朋友,他们所从事的行业与计算机没有太多关联,但是对Python所表现出来的热情,着实让我感到意外。他们选择学习Python的原因大致有两点:第一,认为Python很有用(有需求);第二,认为Python很简单(成本低,主要指时间成本)。那么,作为非计算机行业的从业人员,比如法律或者财税等其他非计算机行业的职业者,是否有学习这项技能的必要?这是我们今天要分享的话题。有言在先,分享这个话题,并不代表我本人的计算机水平达到了某种程度,只是作为一个业余学习Python的“过来人”,尝试对同样准备学习他的朋友们提供一些参考。对于专业选手来讲,本文的一些观点或许并不适用。
Python很有用?编程之路大致有两种途径,一种是先系统地学习编程语言,然后用语言解决遇到的问题;另一种是先有了应用场景需求,然后拆分问题寻找编程的实现方法。第一种属于被动接受的学习方式,也是传统教育普遍采用的培养方式,优点是基础往往比较牢靠,对知识也有全面的认知,缺点是学习的时间比较久,前期看不到成效并且不容易坚持下来。另一种属于主动探索的学习方式,通常是脱离学校教育后的主要学习方式,优点是目标和需求比较明确,能切实地解决问题,缺点也比较明显,没有系统化的指导和培养,往往需要自己寻找解决各种问题的方案,会走一些弯路,需要一点百折不挠的劲头才行。前者,适用于几乎所有的语言学习,后者适用于简单的语言学习,比如Python,也适用于从一门语言向另一门语言转变的学习,比如从C++转学Python。我是接触计算机比较早的一批人,起先学习的编程语言是C++,但是在取得该语言等级认证后的很长一段时间里,我并没有做出过任何一个实用的程序项目,因为如果不从事计算机行业,这些编程语言几乎没有任何应用场景,但Python算是个例外。我使用Python的第一个应用场景是毕业后的租房。当年的豆瓣网还是个很纯粹的平台,上海许多优质的房屋直租信息常常会在这个平台发布,对于有租房需求又没时间泡在平台上的我来讲,通过程序检索指定区域内的租房信息并将信息实时反馈给我,无疑是一个不错的方案,于是我向计算机专业的朋友请教了解决方案,他们向我推荐了Python(相比之下,这个场景用包括C语言在内的其他语言都很难实现),在见识到它强大的网络爬虫功能后,我开启了自己的Python之路。
律师的职业中,Python的应用场景主要围绕着文本文档处理和信息筛查展开,具体可以分为这样几类:(1)文本信息的筛查,尤其是对批量文本进行深度检索。比如,某年在某省高院协调处理地产纠纷诉讼事务的时候,我们对于解除房地产合作协议的诉讼案件收费标准与当地法院有了不同的看法,当地法院倾向于按照合同总价款的比例收取,而我们倾向于按照申请金额的比例收取,两个收费的标准相差了近600多万。为了佐证我们的观点,我们调取了北京、上海、广州等几个重要城市的近500余份裁判文书,并从中提取出诉讼费的裁判数据,形成了一份关于诉讼费收取的专项报告,其中对文书筛选的工作便是通过编程来完成的。(2)网站关键信息检索。比如,在一些官方网站检索功能受限的情况下,通过遍历网页的方式可以较准确地检索到官方公布的数据和文书(此处,忍住吐嘈某度在信息检索方面的商业化运作,以及某些行政类网站信息维护的质量,暂略去1000字)。又比如,在公报案例库还不健全的时候,通过自定义编程建立起自己的数据中心。(3)资料的收集、归纳和整理。比如,某年在研究某集团公司历年来的1100余件重大诉讼案件的过程中,提取历年来的案件信息,汇总整理报告的部分,也是通过编程辅助完成的。
又比如,在企业并购的业务中,需要对某上市公司历年公报信息进行收集,正常情况下历年公布的信息需要逐一下载,这项工作同样也可以通过程序自动处理。(4)格式化文本及文档。包括PDF及OFFICE格式文档操作,格式转换,信息提取与变更等,通常可以很方便地实现多任务的串联操作。对于想要接触Python的朋友,建议大家认真地了解一下编程与自身应用场景需求之间的匹配问题,如果有好的软件可以方便地实现这些应用场景,其实并不需要自行编程处理。比如,一些课程机构宣扬的用Python操作Excel,就我个人的体验来看,其中很多应用场景并不如使用Office办公软件方便,倒是财会的朋友常用的VBA功能可以尝试使用Python作为替换。技术发展至今,编程所能够解决的问题仍然局限于批量的、规律的以及重复性的一些工作,或者把一些分散的独立任务串联到一起,这是编程所能实现的基本任务。如果想要应对那些少量的、不规律的以及一次性的工作,启用编程并不是一个好的选择。因为少量的工作可以径直做完,并不需要通过编程处理(技术化解决的优势并不明显);而不规律的工作场景,由于所需要的个性化细节过多,一般的编程技术难以实现或者技术尚不能实现;对于一次性的工作需要考虑编程花费的时间成本与收获之间的平衡,花费过多而收获却少反倒不如直接处理来的快,这是在实用性能之外需要考虑的成本因素。
当然,有一部分朋友需要用编程解决一些专业性的计算和模拟测试等问题,这不在本文的讨论之列,这类需求应当归入专业需求者的行列,不适用于业余需求者。Python很容易?片面地强调Python学习起来很容易是不对的,事实上,容易与困难始终是相对的。我们可以说Python语言相对于其他语言来讲容易的多,因为有良好的扩展性能,许多应用场景可以由封装完备的库(可以理解为系统安装不同的操作软件,只不过这是非视窗化的软件)来实现,因此Python的程序代码比其他语言要简洁许多。早期最流行的一份学习资料是《简明Python教程》的小册子(原作Swaroop,C.H由沈洁元翻译,适用于python2.0版本),整个文档仅有70多页。支持3.0的版本也不需要购买教程,官方已经提供了最权威的版本(详见),这部分内容用一天的时间学习已经足够,哪怕没有编程经验的新手要学完整个课程也不会超过3天时间,这是该语言的特点,也是该语言“很容易”的重要依据。但是仅凭学完这些,你仍不能解决实际中的任何应用问题,因为需要继续了解和掌握的辅助知识还有很多,主要有两大方面:一方面你需要补充学习支持你需求的各类库,比如支持文本处理的Xlings、PyPDF2、python-docx等库,支持数据分析功能的Numpy、Pandas、Matplatlib等库,每一个库所能支持的功能不同,对操作系统的依赖也不相同,这时候我们往往需要阅读这些库的技术文档并做好试错的心理准备。
另一方面,你还需要补充学习一些其他计算机语言的相关知识,比如掌握基本的Linux或CMD操作命令,对于文字处理需求较多的朋友尤其需要掌握正则表达式(类似于Office中的通配符)的使用方法,对爬虫有需求的朋友仍需要学习网络开发语言或者相关技术(包括HTML、CSS、PHP、JS、JQ、AJAX中的一种或者多种组合)。几乎每个应用场景都需要你了解与这项功能相关联的知识,如果前期你对计算机的熟悉程度比较低的话,这里的知识缺失仍然会使你寸步难行。除此以外,你需要付出的额外成本还包括修正各种出其不意的报错,包括:(1)兼容性错误。比如一些库支持Mac系统并不支持Win系统,你需要在系统与库之间做出新的取舍,再比如系统升级而库未升级导致的新问题,软件冲突问题等。(2)程序本身的错误。有些错误是语言书写不规范导致的,这在学习初期尤其严重,比如以往用于2.0的函数或语法被用在了3.0的程序中,比如常见的数据类型、标点、空格以及由于多字、少字、错字引发的各类程序报错,以及对函数调用过程中的出现的规则错误,等等使你妨不胜妨。(3)网络错误或网络受限。在需要网络的各类编程活动中,通常会遇到网络的问题,比如受网络限制官网指定的Brew的安装方法在很多地区并不能操作成功,需要将官方源调整为民间备用源等。
以上是编程会遇到的普遍问题,那么接下来的技术局限是因人而异的另一重困难。几乎每个人都会经历这一过程,那种苦苦思索仍不得其解的感受,可能会使你兴奋,也可能会使你崩溃,你需要有这方面的心理准备。比如冒泡排序法作为经典的排序算法,几乎是每本程序书中都讲过的内容,有的人总也想不明白,也有些人知道了其中的原理,但就是写不出能够运行的程式。我最近曾尝试编写一个查询汉字五笔编码的工具时,尝试了许多方法也找寻了许多教程,但是仍然没有办法得到指定网页反馈来的结果。对于这种经历,解决的途径无非是两种:一是放弃;二是继续想办法攻克(多数人会选择前一项,这与我们处理工作问题时的遭遇相类似)。前者的一次放弃很可能只是下一次放弃的开端,直到你放弃整个语言。而后者意味着你可能需要投入更多的时间,这种投入可能会成功也可能未必会有收获。
每一个解决问题的人,总会遇到更多的问题,这是解题人的宿命。总的来说,Python在工作中的应用场景毋庸置疑,它几乎包含了所有的需要用到电脑工作的行业,而且未来的应用也很可观。当然,我们并不需要过分地追赶这门语言,毕竟要真正掌握这门语言其实并不算太简单,考虑到我们的应用场景和应用频率,对于已经有主要职业的从业者们来讲,学习过程中的所有困难都将折算成为时间成本,而你付出的成本越大这项技能的性价比就越低,这是每个尝试业余了解这项技能的人需要认真考虑的问题。不过,如果你把它当作业余爱好,那就另当别论了,毕竟编程也是一件和打王者荣耀一样很好玩的事。尤其,提出问题、分析问题、解决问题会伴随着整个编程过程,而这个过程会让我们习惯于拆解各项需求,思考实现的步骤,并为实现需求寻找解决方案,最后达到一步步实现需求的效果,这本身就是一种很好的思维训练。以上是本期Til与您分享的话题。感谢您的关注和支持。
js提取指定网站内容提取方法(form转换提取内容)
网站优化 • 优采云 发表了文章 • 0 个评论 • 75 次浏览 • 2022-04-30 22:01
js提取指定网站内容提取方法也较多,一般针对各网站提取来源网站,有full-text提取方法、有response转换提取方法、还有用webextension提取等,也就是我们平常用的jsxml提取。1.full-text方法,支持jscss等内容提取2.response转换提取,支持jscss等内容提取3.webextension提取,可提取jscss等内容提取方法前两种,建议都可以直接在浏览器提取的话,会很麻烦。建议用用网上的webextension提取方法,或者是通过浏览器的网页调试来提取。
一、full-text提取
1、一条提取10条,这种方法适合前端,在接下来的10条中,同一段内容出现的内容较多,时间不长的情况下。
2、从10条提取到10条内容,这种方法适合前端基础较差,希望速度快,批量提取js,内容较少的情况下。
3、n条提取前10条,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
4、如果条件允许,直接从全网提取,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
二、response转换提取方法(form提取方法)
1、form提取内容,这种方法适合前端基础一般,批量提取css,js等,
2、response提取内容,这种方法适合前端基础一般,批量提取css,js等,
3、批量转换的另一种情况是将请求中的async模块转换成string类型的对象。
4、如果请求中有multipart的参数转换为text类型,适合批量提取js,css等。 查看全部
js提取指定网站内容提取方法(form转换提取内容)
js提取指定网站内容提取方法也较多,一般针对各网站提取来源网站,有full-text提取方法、有response转换提取方法、还有用webextension提取等,也就是我们平常用的jsxml提取。1.full-text方法,支持jscss等内容提取2.response转换提取,支持jscss等内容提取3.webextension提取,可提取jscss等内容提取方法前两种,建议都可以直接在浏览器提取的话,会很麻烦。建议用用网上的webextension提取方法,或者是通过浏览器的网页调试来提取。
一、full-text提取
1、一条提取10条,这种方法适合前端,在接下来的10条中,同一段内容出现的内容较多,时间不长的情况下。
2、从10条提取到10条内容,这种方法适合前端基础较差,希望速度快,批量提取js,内容较少的情况下。
3、n条提取前10条,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
4、如果条件允许,直接从全网提取,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
二、response转换提取方法(form提取方法)
1、form提取内容,这种方法适合前端基础一般,批量提取css,js等,
2、response提取内容,这种方法适合前端基础一般,批量提取css,js等,
3、批量转换的另一种情况是将请求中的async模块转换成string类型的对象。
4、如果请求中有multipart的参数转换为text类型,适合批量提取js,css等。
实用脚本!Python 提取 PDF 指定内容生成新文件!
网站优化 • 优采云 发表了文章 • 0 个评论 • 354 次浏览 • 2022-04-30 00:15
很多时候,我们并不会预知希望提取的页号,而是希望将包含指定内容的页面提取合并为新PDF,本文就以两个真实需求为例进行讲解。
01
需求描述
数据是一份有286页的上市公司公开年报PDF,大致如下
现在需要利用 Python 完成以下两个需求
“
需求一:提取所有包含 战略 二字的页面并合并新PDF
需求二:提取所有包含图片的页面,并分别保存为 PDF 文件
”
02
前置知识和逻辑梳理
2.1 PyPDF2 模块实现合并
PyPDF2 导入模块的代码常常是:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />
这里导入了两个方法:
PdfFileReader 可以理解为读取器PdfFileWriter 可以理解为写入器
利用 PyPDF2 实现合并运用的一下逻辑:
读取器将所有pdf读取一遍读取器将读取的内容交给写入器写入器统一输出到一个新pdf
隐含知识点:读取器只能将读取的内容一页一页交给写入器
2.2 获取与添加页面
之前我们的推文中提到这两个代码,下面列出作为复习:
.getPage 获取特定页.addPage 添加特定页2.3 图片和文字的处理
要实现本文的需求还要做到很重要的一个判断:确定页面中有无包含的文字或图片
判断是否包含特定的文字比较简单,遍历每一页的时候都将包含的文本抽提出,做字符串层面的判断即可,代码思路:
利用 pdfplumber 打开PDF 文件获取指定的页,或者遍历每一页利用 .extract_text() 方法提取当前页的文字判断 “战略” 是否在提取的文字中
判断是否包含图片,思路和上面是类似的,但方法不同。图片考虑用正则的方法识别,用 fitz 和 re 配合,具体见下文代码
03
代码实现
3.1 需求一的实现
首先来完成需求一的任务,导入需要用到的库:读取写入PDF文件的 PyPDF2 以及抽提文本的 pdfplumber
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br />
指定文件所在的路径,同时初始化写入器,将文件交给读取器:
path = r'C:\xxxxxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />
以上下文管理器形式通过 pdfplumber 打开文件,同时用 .getNumPages 获取读取器的最大页利于遍历每一页来抽提文字:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br />
我们抽提文字的目的是用来判断,将符合要求的页码作为读取器 .getPage 的参数,最后用 .addPage 交给写入器:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br />
完成识别后让写入器输出为需要的文件名:
with open(path + r'\new_公司年报.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
至此,我们就完成了包含特定文字内容页面的提取,并整合成一个PDF。所有的页面均包含“战略”二字:
需求一完整代码如下,感兴趣的读者可以自行研究
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br /><br />path = r'C:\xxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br /><br />with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br /><br />with open(path + r'\new_公司年报1.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
3.2 需求二的实现
接下来完成需求二的任务。首先导入需要的库:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br />
指定文件所在的路径:
path = r'C:\xxxxxx'<br />
正则识别图片的部分不细讲,之前的推文已经介绍过,我们直接看代码:
page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br /><br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br />
获取到所有包含图片的页面后,再结合读取器和写入器的配合就能完成新 PDF 的产生。注意本需求是所有图片单独输出,因此获取到页面后交给写入器直接输出成文件:
pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
至此也完成了第二个需求。需要说明的是目前没有非常完美提取PDF图片的方法,本案例介绍的方法识别图片也并不稳定。读者可以利用自己的数据多做尝试。完整代码如下:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br /><br />path = r'C:\xxx'<br /><br />page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br /><br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
实现这两个单个需求后,就可以将相关代码封装并结合os等模块实现批量操作,解放双手。
近期阅读学习推荐:
如何找到我:
<p data-mid="" style="outline: 0px;max-width: 100%;font-size: 14px;font-family: PingFangSC-Medium, "PingFang SC";font-weight: bold;color: rgb(183, 78, 67);line-height: 20px;box-sizing: border-box !important;overflow-wrap: break-word !important;">分享
收藏
点赞
在看</p> 查看全部
实用脚本!Python 提取 PDF 指定内容生成新文件!
很多时候,我们并不会预知希望提取的页号,而是希望将包含指定内容的页面提取合并为新PDF,本文就以两个真实需求为例进行讲解。
01
需求描述
数据是一份有286页的上市公司公开年报PDF,大致如下
现在需要利用 Python 完成以下两个需求
“
需求一:提取所有包含 战略 二字的页面并合并新PDF
需求二:提取所有包含图片的页面,并分别保存为 PDF 文件
”
02
前置知识和逻辑梳理
2.1 PyPDF2 模块实现合并
PyPDF2 导入模块的代码常常是:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />
这里导入了两个方法:
PdfFileReader 可以理解为读取器PdfFileWriter 可以理解为写入器
利用 PyPDF2 实现合并运用的一下逻辑:
读取器将所有pdf读取一遍读取器将读取的内容交给写入器写入器统一输出到一个新pdf
隐含知识点:读取器只能将读取的内容一页一页交给写入器
2.2 获取与添加页面
之前我们的推文中提到这两个代码,下面列出作为复习:
.getPage 获取特定页.addPage 添加特定页2.3 图片和文字的处理
要实现本文的需求还要做到很重要的一个判断:确定页面中有无包含的文字或图片
判断是否包含特定的文字比较简单,遍历每一页的时候都将包含的文本抽提出,做字符串层面的判断即可,代码思路:
利用 pdfplumber 打开PDF 文件获取指定的页,或者遍历每一页利用 .extract_text() 方法提取当前页的文字判断 “战略” 是否在提取的文字中
判断是否包含图片,思路和上面是类似的,但方法不同。图片考虑用正则的方法识别,用 fitz 和 re 配合,具体见下文代码
03
代码实现
3.1 需求一的实现
首先来完成需求一的任务,导入需要用到的库:读取写入PDF文件的 PyPDF2 以及抽提文本的 pdfplumber
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br />
指定文件所在的路径,同时初始化写入器,将文件交给读取器:
path = r'C:\xxxxxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />
以上下文管理器形式通过 pdfplumber 打开文件,同时用 .getNumPages 获取读取器的最大页利于遍历每一页来抽提文字:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br />
我们抽提文字的目的是用来判断,将符合要求的页码作为读取器 .getPage 的参数,最后用 .addPage 交给写入器:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br />
完成识别后让写入器输出为需要的文件名:
with open(path + r'\new_公司年报.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
至此,我们就完成了包含特定文字内容页面的提取,并整合成一个PDF。所有的页面均包含“战略”二字:
需求一完整代码如下,感兴趣的读者可以自行研究
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br /><br />path = r'C:\xxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br /><br />with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br /><br />with open(path + r'\new_公司年报1.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
3.2 需求二的实现
接下来完成需求二的任务。首先导入需要的库:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br />
指定文件所在的路径:
path = r'C:\xxxxxx'<br />
正则识别图片的部分不细讲,之前的推文已经介绍过,我们直接看代码:
page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br /><br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br />
获取到所有包含图片的页面后,再结合读取器和写入器的配合就能完成新 PDF 的产生。注意本需求是所有图片单独输出,因此获取到页面后交给写入器直接输出成文件:
pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
至此也完成了第二个需求。需要说明的是目前没有非常完美提取PDF图片的方法,本案例介绍的方法识别图片也并不稳定。读者可以利用自己的数据多做尝试。完整代码如下:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br /><br />path = r'C:\xxx'<br /><br />page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br /><br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
实现这两个单个需求后,就可以将相关代码封装并结合os等模块实现批量操作,解放双手。
近期阅读学习推荐:
如何找到我:
收藏
点赞
在看</p>
js提取指定网站内容(js提取指定网站内容依赖进行处理方法介绍)
网站优化 • 优采云 发表了文章 • 0 个评论 • 48 次浏览 • 2022-04-19 19:01
js提取指定网站内容:采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。
js提取指定网站内容采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。④将指定的html格式存储到html文件中。
js提取指定网站内容,采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容,采用json读取,
自己处理自己网站内容,除了通过json格式存储外,自己还可以写个业务逻辑,判断自己网站的内容是否有误,如果有误就调用系统生成的文档进行校验。 查看全部
js提取指定网站内容(js提取指定网站内容依赖进行处理方法介绍)
js提取指定网站内容:采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。
js提取指定网站内容采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。④将指定的html格式存储到html文件中。
js提取指定网站内容,采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容,采用json读取,
自己处理自己网站内容,除了通过json格式存储外,自己还可以写个业务逻辑,判断自己网站的内容是否有误,如果有误就调用系统生成的文档进行校验。
js提取指定网站内容(优质文章,第一时间送达!(掘金GNE)|掘金)
网站优化 • 优采云 发表了文章 • 0 个评论 • 65 次浏览 • 2022-04-18 16:39
高品质文章,尽快发货!
正文 | 青南
出处 | 掘金
GNE(GeneralNewsExtractor)是一个综合新闻网站文本提取模块,输入一个新闻网页的HTML,输出文本中的文本内容、标题、作者、发布时间、图片地址和源代码文本所在的标签。GNE在提取今日头条、网易新闻、游民新闻、观察者网、凤凰网、腾讯新闻、ReadHub、新浪新闻等数百条中文新闻网站方面非常有效,准确率几乎可以达到100% . .
使用非常简单:
from gne import GeneralNewsExtractor
extractor = GeneralNewsExtractor
html = '网站源代码'
result = extractor.extract(html)
print(result)
GNE的输入是js渲染后的HTML代码,所以GNE可以配合Selenium或者Pyppeteer使用。
下图是GNE用Selenium实现的Demo:
对应的代码是:
import time
from gne import GeneralNewsExtractor
from selenium.webdriver import Chrome
driver = Chrome('./chromedriver')
driver.get('https://www.toutiao.com/a6766986211736158727/')
time.sleep(3)
extractor = GeneralNewsExtractor
result = extractor.extract(driver.page_source)
print(result)
下图是GNE用Pyppeteer实现的Demo:
对应的代码如下:
import asyncio
from gne import GeneralNewsExtractor
from pyppeteer import launch
async def main:
browser = await launch(executablePath='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome')
page = await browser.newPage
await page.goto('https://news.163.com/20/0101/17/F1QS286R000187R2.html')
extractor = GeneralNewsExtractor
result = extractor.extract(await page.content)
print(result)
input('检查完成以后回到这里按下任意键')
asyncio.run(main)
如何安装 GNE
现在您可以使用 pip 直接安装 GNE:
pip install gne
如果访问官方pypi源太慢,也可以使用网易源:
pip install gne -i https://mirrors.163.com/pypi/simple/
安装过程如下图所示:
功能获取文本源代码
当 extract 方法只传入网页的源代码而不添加任何额外的参数时,GNE 返回以下字段:
有的朋友可能想获取新闻正文所在标签的源码。这时候可以将with_body_html参数传给extract方法,设置为True:
extractor = GeneralNewsExtractor
extractor.extract(html, with_body_html=True)
返回的数据中会添加一个字段body_html,其值为body对应的HTML源代码。
运行效果如下图所示:
总是返回图像的绝对路径
默认情况下,如果新闻中的图片使用相对路径,则GNE返回的images字段对应的值也是图片的相对路径列表。
如果想让 GNE 总是返回绝对路径,那么可以在 extract 方法中添加 host 参数,这个参数的值就是图片的域名,例如:
extractor = GeneralNewsExtractor
extractor.extract(html, host='https://www.kingname.info')
这样,如果新闻中的图片是/images/pic.png,GNE会自动将其更改为。
指定新闻标题所在的 XPath
GNE 预定义了一组用于提取新闻标题的 XPath 和正则表达式。但是,一些特殊新闻网站可能无法提取标题。在这种情况下,您可以在 extract 方法中指定 title_xpath 参数来提取新闻标题:
extractor = GeneralNewsExtractor
extractor.extract(html, title_xpath='//title/text')
尽早去除嘈杂的标签
一些新闻下可能会有长篇大论的评论。这些评论看起来比新闻文本“更像”。为了防止它们干扰新闻提取,可以将noise_node_list参数传递给extract方法,提前去除这些噪声节点。noise_node_list 的值是一个或多个 XPath 的列表:
extractor = GeneralNewsExtractor
extractor.extract(html, noise_node_list=['//div[@class="comment-list"]', '//*[@style="display:none"]'])
使用配置文件
API中的参数title_xpath、host、noise_node_list、with_body_html除了直接写入extract方法外,还可以通过配置文件进行设置。
请在项目的根目录中创建一个文件 .gne。配置文件可以是 YAML 格式,也可以是 JSON 格式。
title:
xpath: //title/text
host: https://www.xxx.com
noise_node_list:
- //div[@class="comment-list"]
- //*[@style="display:none"]
with_body_html: true
{
"title": {
"xpath": "//title/text"
},
"host": "https://www.xxx.com",
"noise_node_list": ["//div[@class="comment-list"]",
"//*[@style="display:none"]"],
"with_body_html": true
}
这两个符号是完全等价的。
配置文件和extract方法的参数一样,并不是所有的字段都需要提供。您可以组合并填写您需要的字段。
如果 extract 方法和 .gne 配置文件中的参数具有不同的值,则 extract 方法中的参数具有更高的优先级。
FAQGeneralNewsExtractor(以下简称GNE)是爬虫吗?
GNE 不是爬虫,它的项目名称 General News Extractor 代表 General News Extractor。它的输入是HTML,它的输出是一个收录新闻标题、新闻正文、作者、出版时间的字典。您需要找到自己的方式来获取目标页面的 HTML。
GNE 不会也不会在未来提供请求网页的功能。
GNE 支持翻页吗?
GNE 不支持翻页。因为GNE不提供网页请求的功能,所以需要自己获取每个页面的HTML,单独传给GNE。
GNE 支持哪些版本的 Python?
不少于 Python 3.6.0
我把requests/Scrapy得到的HTML传给GNE,为什么不能提取body?
GNE 基于 HTML 提取文本,因此传入的 HTML 必须是 JavaScript 渲染的 HTML。但是,requests 和 Scrapy 只在 JavaScript 渲染之前获取源代码,因此无法正确提取。
此外,还有一些网页,比如今日头条,其新闻文本实际上是直接用JSON格式的网页源代码编写的。当页面在浏览器上打开时,JavaScript 将源代码中的文本解析为 HTML。在这种情况下,您不会在 Chrome 上看到 Ajax 请求。
所以建议大家使用 Puppeteer/Pyppeteer/Selenium 等工具来获取渲染后的 HTML 并传入 GNE。
原文链接: 查看全部
js提取指定网站内容(优质文章,第一时间送达!(掘金GNE)|掘金)
高品质文章,尽快发货!
正文 | 青南
出处 | 掘金
GNE(GeneralNewsExtractor)是一个综合新闻网站文本提取模块,输入一个新闻网页的HTML,输出文本中的文本内容、标题、作者、发布时间、图片地址和源代码文本所在的标签。GNE在提取今日头条、网易新闻、游民新闻、观察者网、凤凰网、腾讯新闻、ReadHub、新浪新闻等数百条中文新闻网站方面非常有效,准确率几乎可以达到100% . .
使用非常简单:
from gne import GeneralNewsExtractor
extractor = GeneralNewsExtractor
html = '网站源代码'
result = extractor.extract(html)
print(result)
GNE的输入是js渲染后的HTML代码,所以GNE可以配合Selenium或者Pyppeteer使用。
下图是GNE用Selenium实现的Demo:
对应的代码是:
import time
from gne import GeneralNewsExtractor
from selenium.webdriver import Chrome
driver = Chrome('./chromedriver')
driver.get('https://www.toutiao.com/a6766986211736158727/')
time.sleep(3)
extractor = GeneralNewsExtractor
result = extractor.extract(driver.page_source)
print(result)
下图是GNE用Pyppeteer实现的Demo:
对应的代码如下:
import asyncio
from gne import GeneralNewsExtractor
from pyppeteer import launch
async def main:
browser = await launch(executablePath='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome')
page = await browser.newPage
await page.goto('https://news.163.com/20/0101/17/F1QS286R000187R2.html')
extractor = GeneralNewsExtractor
result = extractor.extract(await page.content)
print(result)
input('检查完成以后回到这里按下任意键')
asyncio.run(main)
如何安装 GNE
现在您可以使用 pip 直接安装 GNE:
pip install gne
如果访问官方pypi源太慢,也可以使用网易源:
pip install gne -i https://mirrors.163.com/pypi/simple/
安装过程如下图所示:
功能获取文本源代码
当 extract 方法只传入网页的源代码而不添加任何额外的参数时,GNE 返回以下字段:
有的朋友可能想获取新闻正文所在标签的源码。这时候可以将with_body_html参数传给extract方法,设置为True:
extractor = GeneralNewsExtractor
extractor.extract(html, with_body_html=True)
返回的数据中会添加一个字段body_html,其值为body对应的HTML源代码。
运行效果如下图所示:
总是返回图像的绝对路径
默认情况下,如果新闻中的图片使用相对路径,则GNE返回的images字段对应的值也是图片的相对路径列表。
如果想让 GNE 总是返回绝对路径,那么可以在 extract 方法中添加 host 参数,这个参数的值就是图片的域名,例如:
extractor = GeneralNewsExtractor
extractor.extract(html, host='https://www.kingname.info')
这样,如果新闻中的图片是/images/pic.png,GNE会自动将其更改为。
指定新闻标题所在的 XPath
GNE 预定义了一组用于提取新闻标题的 XPath 和正则表达式。但是,一些特殊新闻网站可能无法提取标题。在这种情况下,您可以在 extract 方法中指定 title_xpath 参数来提取新闻标题:
extractor = GeneralNewsExtractor
extractor.extract(html, title_xpath='//title/text')
尽早去除嘈杂的标签
一些新闻下可能会有长篇大论的评论。这些评论看起来比新闻文本“更像”。为了防止它们干扰新闻提取,可以将noise_node_list参数传递给extract方法,提前去除这些噪声节点。noise_node_list 的值是一个或多个 XPath 的列表:
extractor = GeneralNewsExtractor
extractor.extract(html, noise_node_list=['//div[@class="comment-list"]', '//*[@style="display:none"]'])
使用配置文件
API中的参数title_xpath、host、noise_node_list、with_body_html除了直接写入extract方法外,还可以通过配置文件进行设置。
请在项目的根目录中创建一个文件 .gne。配置文件可以是 YAML 格式,也可以是 JSON 格式。
title:
xpath: //title/text
host: https://www.xxx.com
noise_node_list:
- //div[@class="comment-list"]
- //*[@style="display:none"]
with_body_html: true
{
"title": {
"xpath": "//title/text"
},
"host": "https://www.xxx.com",
"noise_node_list": ["//div[@class="comment-list"]",
"//*[@style="display:none"]"],
"with_body_html": true
}
这两个符号是完全等价的。
配置文件和extract方法的参数一样,并不是所有的字段都需要提供。您可以组合并填写您需要的字段。
如果 extract 方法和 .gne 配置文件中的参数具有不同的值,则 extract 方法中的参数具有更高的优先级。
FAQGeneralNewsExtractor(以下简称GNE)是爬虫吗?
GNE 不是爬虫,它的项目名称 General News Extractor 代表 General News Extractor。它的输入是HTML,它的输出是一个收录新闻标题、新闻正文、作者、出版时间的字典。您需要找到自己的方式来获取目标页面的 HTML。
GNE 不会也不会在未来提供请求网页的功能。
GNE 支持翻页吗?
GNE 不支持翻页。因为GNE不提供网页请求的功能,所以需要自己获取每个页面的HTML,单独传给GNE。
GNE 支持哪些版本的 Python?
不少于 Python 3.6.0
我把requests/Scrapy得到的HTML传给GNE,为什么不能提取body?
GNE 基于 HTML 提取文本,因此传入的 HTML 必须是 JavaScript 渲染的 HTML。但是,requests 和 Scrapy 只在 JavaScript 渲染之前获取源代码,因此无法正确提取。
此外,还有一些网页,比如今日头条,其新闻文本实际上是直接用JSON格式的网页源代码编写的。当页面在浏览器上打开时,JavaScript 将源代码中的文本解析为 HTML。在这种情况下,您不会在 Chrome 上看到 Ajax 请求。
所以建议大家使用 Puppeteer/Pyppeteer/Selenium 等工具来获取渲染后的 HTML 并传入 GNE。
原文链接:
js提取指定网站内容(波波带你一步一步手动提取网页视频(图))
网站优化 • 优采云 发表了文章 • 0 个评论 • 49 次浏览 • 2022-04-18 00:02
在生活中,我们经常会看一些网络视频,其中一些感觉非常好,我们都希望自己下载下来采集,但是却常常尝试失败。
这次博博会带你一步步手动提取网络视频。
首先,需要一些准备知识。
一、网络视频
网页中的视频一般是通过视频标签来播放的,还会有一些其他的技术手段来避免或隐藏视频。避开视频可能会使用其他一些播放器来播放视频,隐藏视频一般会将视频放在一个iframe中,这个后续具体案例会介绍。
二、视频链接类型
网页中的大部分视频都会有 mp4 链接和 m3u8 链接。前者易于提取和下载,而后者则比较麻烦。
三、谷歌浏览器
谷歌浏览器是我们下一步的关键。当然,一些360浏览器、QQ浏览器等自带chrome内核的浏览器也是可以的。
在chrome浏览器中,我们需要熟悉控制台,可以通过F12调出,或者在菜单中打开JavaScript控制台。具体可以从百度打开到不同的浏览器,一般是windows的F12。
如下图所示,控制台看起来大致。后期我们会用到Elements、Network、Application,主要是Network,其次是Elements。可能会用到一些应用场景。左上角还有一个箭头按钮,可以用来定位视频标签。
基础的就先到这里,后面再做一些实际操作。
期待下一个文章。 查看全部
js提取指定网站内容(波波带你一步一步手动提取网页视频(图))
在生活中,我们经常会看一些网络视频,其中一些感觉非常好,我们都希望自己下载下来采集,但是却常常尝试失败。
这次博博会带你一步步手动提取网络视频。
首先,需要一些准备知识。
一、网络视频
网页中的视频一般是通过视频标签来播放的,还会有一些其他的技术手段来避免或隐藏视频。避开视频可能会使用其他一些播放器来播放视频,隐藏视频一般会将视频放在一个iframe中,这个后续具体案例会介绍。
二、视频链接类型
网页中的大部分视频都会有 mp4 链接和 m3u8 链接。前者易于提取和下载,而后者则比较麻烦。
三、谷歌浏览器
谷歌浏览器是我们下一步的关键。当然,一些360浏览器、QQ浏览器等自带chrome内核的浏览器也是可以的。
在chrome浏览器中,我们需要熟悉控制台,可以通过F12调出,或者在菜单中打开JavaScript控制台。具体可以从百度打开到不同的浏览器,一般是windows的F12。
如下图所示,控制台看起来大致。后期我们会用到Elements、Network、Application,主要是Network,其次是Elements。可能会用到一些应用场景。左上角还有一个箭头按钮,可以用来定位视频标签。
基础的就先到这里,后面再做一些实际操作。
期待下一个文章。
js提取指定网站内容(.js,request和cheerio在我学校的课程表网站上抓取链接)
网站优化 • 优采云 发表了文章 • 0 个评论 • 50 次浏览 • 2022-04-17 16:01
我正在尝试使用 Node.js、request 和 Cheerio 来抓取我学校课程表上的链接网站。但是,我的代码并未到达所有主题链接。
此处链接到课程表网站。
这是我的代码:
var express = require('express');
var request = require('request');
var cheerio = require('cheerio');
var app = express();
app.get('/subjects', function(req, res) {
var URL = 'http://courseschedules.njit.ed ... 3B%3B
request(URL, function(error, response, body) {
if(!error) {
var $ = cheerio.load(body);
$('.courseList_section a').each(function() {
var text = $(this).text();
var link = $(this).attr('href');
console.log(text + ' --> ' + link);
});
}
else {
console.log('There was an error!');
}
});
});
app.listen('8080');
console.log('Magic happens on port 8080!');
我的输出可以在这里找到。
从我的输出中可以看出,缺少一些链接。更具体地说,来自“A”、“I(续)”和 R“(续)”部分的链接。这些也是每列的第一部分。
每个部分都收录在自己的 div 中,类名为“courseList_section”,所以我不明白为什么“.courseList_section a”没有遍历所有链接。我错过了一些明显的东西吗?非常感谢任何和所有见解。
提前谢谢你! 查看全部
js提取指定网站内容(.js,request和cheerio在我学校的课程表网站上抓取链接)
我正在尝试使用 Node.js、request 和 Cheerio 来抓取我学校课程表上的链接网站。但是,我的代码并未到达所有主题链接。
此处链接到课程表网站。
这是我的代码:
var express = require('express');
var request = require('request');
var cheerio = require('cheerio');
var app = express();
app.get('/subjects', function(req, res) {
var URL = 'http://courseschedules.njit.ed ... 3B%3B
request(URL, function(error, response, body) {
if(!error) {
var $ = cheerio.load(body);
$('.courseList_section a').each(function() {
var text = $(this).text();
var link = $(this).attr('href');
console.log(text + ' --> ' + link);
});
}
else {
console.log('There was an error!');
}
});
});
app.listen('8080');
console.log('Magic happens on port 8080!');
我的输出可以在这里找到。
从我的输出中可以看出,缺少一些链接。更具体地说,来自“A”、“I(续)”和 R“(续)”部分的链接。这些也是每列的第一部分。
每个部分都收录在自己的 div 中,类名为“courseList_section”,所以我不明白为什么“.courseList_section a”没有遍历所有链接。我错过了一些明显的东西吗?非常感谢任何和所有见解。
提前谢谢你!
js提取指定网站内容(js提取指定网站内容,我自己用php封装了一个go-js-main.php,)
网站优化 • 优采云 发表了文章 • 0 个评论 • 87 次浏览 • 2022-04-17 03:01
js提取指定网站内容,我自己用php封装了一个go-js-main.php,用js调用go函数,生成go页面,然后js用php的ajax去调用,来展示指定的内容。go-js-main.php:go-js-main.phpsendto_some_msgphp提取指定站点内容,还是js封装了一个go.php,然后用php的ajax发送请求,来调用go生成的内容,就可以提取出来。go.php:go.phpsendto_some_msg。
可以在js中判断method而不是url比如xml转码erv。php/xml/source:php:6。2。0/usr/jasmine/commonjs/error。php请求页面的时候xml={"name":"rq@gmail。com","version":1。0,"province":"江苏省无锡市","shipper_pro":"echo","port":"33018","confirm_time":"2018-08-1314:10:42","placement":"phpstorm","request_url":"","response_url":""}。
可以看看golang写的web服务器,提供mysql的访问。(golang2.6:)可以看看golang的后端服务器生态。
java模块化开发,数据接口可按需服务。这些it产品背后是具有web架构体系的巨大体系。
很多啊java可以gorustpythonmicrosoft都有boost 查看全部
js提取指定网站内容(js提取指定网站内容,我自己用php封装了一个go-js-main.php,)
js提取指定网站内容,我自己用php封装了一个go-js-main.php,用js调用go函数,生成go页面,然后js用php的ajax去调用,来展示指定的内容。go-js-main.php:go-js-main.phpsendto_some_msgphp提取指定站点内容,还是js封装了一个go.php,然后用php的ajax发送请求,来调用go生成的内容,就可以提取出来。go.php:go.phpsendto_some_msg。
可以在js中判断method而不是url比如xml转码erv。php/xml/source:php:6。2。0/usr/jasmine/commonjs/error。php请求页面的时候xml={"name":"rq@gmail。com","version":1。0,"province":"江苏省无锡市","shipper_pro":"echo","port":"33018","confirm_time":"2018-08-1314:10:42","placement":"phpstorm","request_url":"","response_url":""}。
可以看看golang写的web服务器,提供mysql的访问。(golang2.6:)可以看看golang的后端服务器生态。
java模块化开发,数据接口可按需服务。这些it产品背后是具有web架构体系的巨大体系。
很多啊java可以gorustpythonmicrosoft都有boost
js提取指定网站内容(js提取指定网站内容是什么?怎么做好php提取)
网站优化 • 优采云 发表了文章 • 0 个评论 • 40 次浏览 • 2022-04-15 12:03
js提取指定网站内容或者是加入到任何一种后端语言的数据库中,比如百度抓取的是搜索引擎的数据,抓取的是数据库里的数据,微信抓取的是网页数据等等。js接收的只是一个字符串,js加在百度搜索页面上的,它把抓取到的网页数据提取出来存入后端语言数据库里,当然能加载进来。并没有什么语言之间的转换,再说这个速度慢到谁也无法接受的程度了。
所以,你抓取数据并不需要把原网页上所有的字符串都转换,不然谁写得出这么丑陋的js库。而是抓取要抓取的网页数据,并加上你需要的数据,然后就可以拿到你需要的数据了。
同意楼上的。
有没有可能是因为你所抓取的网页是空白的啊
你需要加一个前置条件js加载任意网页。网页空白就是没有js,那这个肯定是不能抓取到。
楼上的问题,要看具体网站,如果对一些js完全无感知,比如一些静态站点(大部分是用jquery写的),抓包后得到js是会有框框让它提示js错误的,虽然ie网页抓取插件有个jsinvalidatexss绕过的选项,但是大部分情况下js是不需要显示的,比如@contentdata2d/xxx.js就是直接提示正在加载中,直接禁止执行,不会给服务器提示js错误。
这种方式抓取的只是网页的静态的js文件,apache提供一个nginx,网站抓取的时候加入到php中,提交给php做解析,并不存在图片等内容。如果是在web网站抓取方面,有些web前端和web后端是在一个项目内部写好的,对网站模块不是很懂的可以学一下iis的web配置,一个静态网站全是js。 查看全部
js提取指定网站内容(js提取指定网站内容是什么?怎么做好php提取)
js提取指定网站内容或者是加入到任何一种后端语言的数据库中,比如百度抓取的是搜索引擎的数据,抓取的是数据库里的数据,微信抓取的是网页数据等等。js接收的只是一个字符串,js加在百度搜索页面上的,它把抓取到的网页数据提取出来存入后端语言数据库里,当然能加载进来。并没有什么语言之间的转换,再说这个速度慢到谁也无法接受的程度了。
所以,你抓取数据并不需要把原网页上所有的字符串都转换,不然谁写得出这么丑陋的js库。而是抓取要抓取的网页数据,并加上你需要的数据,然后就可以拿到你需要的数据了。
同意楼上的。
有没有可能是因为你所抓取的网页是空白的啊
你需要加一个前置条件js加载任意网页。网页空白就是没有js,那这个肯定是不能抓取到。
楼上的问题,要看具体网站,如果对一些js完全无感知,比如一些静态站点(大部分是用jquery写的),抓包后得到js是会有框框让它提示js错误的,虽然ie网页抓取插件有个jsinvalidatexss绕过的选项,但是大部分情况下js是不需要显示的,比如@contentdata2d/xxx.js就是直接提示正在加载中,直接禁止执行,不会给服务器提示js错误。
这种方式抓取的只是网页的静态的js文件,apache提供一个nginx,网站抓取的时候加入到php中,提交给php做解析,并不存在图片等内容。如果是在web网站抓取方面,有些web前端和web后端是在一个项目内部写好的,对网站模块不是很懂的可以学一下iis的web配置,一个静态网站全是js。
js提取指定网站内容 真香系列-JSFinder实用改造
网站优化 • 优采云 发表了文章 • 0 个评论 • 78 次浏览 • 2022-06-22 08:53
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付! 查看全部
js提取指定网站内容 真香系列-JSFinder实用改造
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付!
js提取指定网站内容 真香系列-JSFinder实用改造
网站优化 • 优采云 发表了文章 • 0 个评论 • 52 次浏览 • 2022-06-20 02:45
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付! 查看全部
js提取指定网站内容 真香系列-JSFinder实用改造
点击上方蓝字关注我吧!
1.前言
JSFinder是一款优秀的github开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子域名。
github链接:https://github.com/Threezh1/JSFinder
用于提取的正则表达式参考了LinkFinder
SFinder获取URL和子域名的方式:
一些简单的使用方式:
简单爬取
python JSFinder.py -u http://www.mi.com<br />#这个命令会爬取 http://www.mi.com 这单个页面的所有的js链接,并在其中发现url和子域名
深度爬取
python JSFinder.py -u http://www.mi.com -d<br />#深入一层页面爬取JS,时间会消耗的更长,建议使用-ou 和 -os来指定保存URL和子域名的文件名python JSFinder.py -u http://www.mi.com -d -ou mi_url.txt -os mi_subdomain.txt
批量指定URL/指定JS
指定URL:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不需要加深度爬取,单个页面即可,等等,这可以去github上面看使用说明。
2.改造
2.1 为什么要改造这个东西?
因为我经常使用这款工具,我发现了很多不足之处,比如说,如果爬取一个大型一点的,会发现很多url,接口,但是大多数都是404,没有用处的,就是通过人工去筛选就得费好长一段时间,我有一次爬下来了1200多条,密密麻麻............................
所有我的设想是可以增加一个验证模块,进行简单的验证,扔掉那些不存在的url链接,减少人工的筛选。
2.2 找到源码一顿改(验证模块)
改源码一定要找到关键点改,我这里直接在它进行数据处理的时候加入我想要的东西:
thread_num = 0 info = '访问成功' lock = threading.Lock() if urls == None: return None find_url_all_num = len(urls) content_url = "" content_subdomain = "" if self.args.verify !=0: print("A total of Find " + str(len(urls)) + " URL:\n") print("-----------------------But further validation is needed-----------------!\n\n\n") domian_text = requests.get(domian,verify =False).text print("The length of the page currently visited =>"+str(len(domian_text))) result ={} for url in urls: thread_num += 1 self.therads_op(url, content_url, lock,thread_num,result) if thread_num == 100: time.sleep(1) find_url_success_num = 0 for length,url_list in result.items(): print("-----------------------The return packet length is :{len}------------------------".format(len =length)) for url in url_list: print(url+"\n") find_url_success_num += 1 content_url+=url+"\n"
关键的一些代码,这里因为使用了网络验证,所以写了个多线程:
def therads_op(self,url,content_url,lock,thread_num,result): threading.Thread(target=self.request(url,content_url,lock,result),args=(url,content_url,lock,result,)) if lock.acquire(): thread_num -= 1 lock.release()
验证模块:
def request(self,url,content_url,lock,result): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", } try: resp = requests.get(url,verify = False,timeout = 10,headers=headers) if resp.status_code != 404 and lock.acquire(): content_url += url + "\n" if result.get(str(len(resp.text)), 0) == 0: result[str(len(resp.text))] = [] result[str(len(resp.text))].append(url) else: result[str(len(resp.text))].append(url) lock.release()<br /> except Exception as e: pass finally: try: lock.release() except RuntimeError as e: pass
这里我是直接判断它返回值是不是404,当然你也可以加入你自己的判断条件,可以看到我的源码里,有计数返回包的长度,因为我发现很多包的返回包都是一样的,所以我这里判断长度,进行归类,有利于我们自己人工筛选,我们只需要得到任意长度的一个url返回包,就可以知道其他有着相同长度的url返回的内容(这就是我当时的想法吧)
2.3 找到源码一阵改(输出数据格式)
因为原工具是有把输出结果输出到一个文件的功能,但是我感觉不够直观,所以我把输出结果转换成了html文件,可以直接点击url,进行访问,方便了很多。
if self.args.output_html !=None: table_tr0 = '' html = html_Template() total_str = '共url: %s,访问成功:%s,失败 %s' % (find_url_all_num, find_url_success_num, find_url_all_num-find_url_success_num) if self.args.verify !=0: for length,url_list in result.items(): for url in url_list: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length=length, url=url, result=info, ask_url=url_a, ) table_tr0 += table_td else: for url in urls: url_a = "<a href={url}>点击</a>".format(url=url) table_td = html.TABLE_TMPL % dict(length="无法获取", url=url, result=info, ask_url=url_a, ) table_tr0 += table_td output = html.HTML_TMPL % dict(domain=self.args.url,value=total_str, table_tr=table_tr0, ) # 生成html报告 filename = '{date}_{url}.html'.format(date=time.strftime('%Y%m%d%H%M%S'),url = self.args.output_html) dir = str(os.getcwd()) filename = os.path.join(dir, filename) with open(filename, 'wb') as f: f.write(bytes(output, "utf-8"))
我把源码改成了一个类的形式,有利于以后的加入到大项目中,积小成多!
2.4 效果预览
在没有加验证参数的情况下:
开启验证的情况下:
3.总结
本来还想加一个爬虫模块进去的,但是作者有自己的爬虫模块,就算了,如果可以的话,也可以把一些优秀的开源爬虫加进去,就真的很nice了,我以后再加把,先这样吧,运行有什么问题可以及时联系我,越改越实用。
挺香的!真香,找个机会把源码放到github上面去:
exe程序百度云链接:
链接:https://pan.baidu.com/s/17WIa94fr5EAHgfo4UI6Eyw 提取码:qq0c 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
END
看完记得点赞,关注哟,爱您!
请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付!
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 66 次浏览 • 2022-06-17 02:02
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 71 次浏览 • 2022-06-13 11:25
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
js提取指定网站内容是什么?如何提升target空间?
网站优化 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-06-08 21:08
js提取指定网站内容,然后存储到数据库,以后访问就可以拉到n页,第一页也一样。这样做当然就会存在大量重复页,可以写个判断,要求用户移除即可。
优点是页面会变得清爽简洁,加载的内容会减少。缺点是当量增加时,服务器压力加大。浏览器延迟加长,页面转化率低。
占有率统计,跳转列表,
人工拉页就ok,这样有可能会造成一个单页面的上拉列表带动很多页面上拉的情况(这个正常,不要理解偏差。要把页面容量定得小点,动态绑定两个事件,直接从api里给他连接a.addeventlistener('target',path.join({scrolltop:1,scrolltopleft:1}),axios.action({expressive:true})),此外,你定的时间点很重要,如果早上发现设定好了时间点出问题,很崩溃,就要纠结下自己设定的时间点的问题了。
额,上面的方法应该是两种,但是题主你加上空格,
如果网站只是内容的增删,就每页增加一个n(n取决于内容量),每页移动一个n个dom(例如只有一个或者空的dom页,所以每页在n个dom中移动)即可。没有必要全部都要自己定义布局。如果涉及到的新增和删除这两个操作都有了并且在数据库里有索引的话可以使用z-index实现。提升空间肯定是有的,但可行性并不高。用jquery的话还能实现批量操作布局。 查看全部
js提取指定网站内容是什么?如何提升target空间?
js提取指定网站内容,然后存储到数据库,以后访问就可以拉到n页,第一页也一样。这样做当然就会存在大量重复页,可以写个判断,要求用户移除即可。
优点是页面会变得清爽简洁,加载的内容会减少。缺点是当量增加时,服务器压力加大。浏览器延迟加长,页面转化率低。
占有率统计,跳转列表,
人工拉页就ok,这样有可能会造成一个单页面的上拉列表带动很多页面上拉的情况(这个正常,不要理解偏差。要把页面容量定得小点,动态绑定两个事件,直接从api里给他连接a.addeventlistener('target',path.join({scrolltop:1,scrolltopleft:1}),axios.action({expressive:true})),此外,你定的时间点很重要,如果早上发现设定好了时间点出问题,很崩溃,就要纠结下自己设定的时间点的问题了。
额,上面的方法应该是两种,但是题主你加上空格,
如果网站只是内容的增删,就每页增加一个n(n取决于内容量),每页移动一个n个dom(例如只有一个或者空的dom页,所以每页在n个dom中移动)即可。没有必要全部都要自己定义布局。如果涉及到的新增和删除这两个操作都有了并且在数据库里有索引的话可以使用z-index实现。提升空间肯定是有的,但可行性并不高。用jquery的话还能实现批量操作布局。
js提取指定网站内容是什么?提取原理是怎样的?
网站优化 • 优采云 发表了文章 • 0 个评论 • 39 次浏览 • 2022-06-05 05:02
js提取指定网站内容一般就是提取整个网站的所有链接。在提取链接的时候主要就是对链接进行分析,找到指定页面的索引号,在一些社交网站和各大网站,一般都会发布自己网站的链接,也就是说只要找到了指定网站的指定页面,那么找到的这个网站的链接就自然而然是网站内容了。提取整个网站和仅提取某些页面是大相径庭的。链接提取原理:1.一个页面有多个链接需要分析出哪个页面代表着网站内容主页的指定页面,再对其里面的链接进行一一对应,打点就可以。
2.网站所有页面所有链接里有唯一的一个,那么此时也可以打个标记,打出该标记的,就可以认为是所有网站内容的主页,再对其进行提取查询即可。3.根据网站提取所需内容的属性,单独查询每个页面的链接,其内容只有指定页面的内容,提取查询即可。4.找到唯一符合需求的页面,也就是在列表页中的搜索框中,输入关键词后,你可以找到搜索内容,根据页面的内容来匹配内容,就可以得到所有网站内容的索引号。
5.从该页面的链接中提取,确定该页面具体是哪个搜索内容的页面,在列表页内搜索关键词后,可以找到该页面的内容,匹配页面内容(指定页面内容),就可以找到所有网站内容的索引号。6.提取完全需要的页面内容后,再根据实际需求进行查询即可。7.利用google的关键词功能,提取出关键词出来,就可以写一个spider进行搜索,一般网站如果内容很多很杂,且每个页面都需要内容,就会分两个号进行爬取,一个搜索关键词,一个搜索内容(即spider),爬取方式可以是每次爬取一个页面,查找后进行二次搜索。
备注:webspider在爬取的时候,会对全站爬取,因此不建议采用第一种方式,目前常用一种方式就是提取单个页面的链接后,再通过打点找到对应的指定页面内容,再提取出来,这样做的好处在于每个网站内容的种类不同,自然查找的内容也会有差异,也不太会造成流量的损失,相对来说,这种方式比较安全。webspider一般是通过谷歌搜索,搜索一些大词或者比较火的词,注意一定是要大词,不能通过一些比较冷的词,小词来搜索。
再根据页面的内容再进行分析,提取出指定网站内容。如果是爬取某些大站的内容,比如百度首页内容,可以先通过谷歌网站快照找到关键词,例如:某个站点上这个关键词,排在前50位的是谁,等,然后就是在页面后面添加关键词,例如:“”,页面抓取后,通过分析每个页面的内容,提取出需要的内容,如果是想对某一个网站页面内容或者整个网站内容进行抓取,那么就要对该网站的信息分析,清楚的知道这个网站的所有页面内容,并在。 查看全部
js提取指定网站内容是什么?提取原理是怎样的?
js提取指定网站内容一般就是提取整个网站的所有链接。在提取链接的时候主要就是对链接进行分析,找到指定页面的索引号,在一些社交网站和各大网站,一般都会发布自己网站的链接,也就是说只要找到了指定网站的指定页面,那么找到的这个网站的链接就自然而然是网站内容了。提取整个网站和仅提取某些页面是大相径庭的。链接提取原理:1.一个页面有多个链接需要分析出哪个页面代表着网站内容主页的指定页面,再对其里面的链接进行一一对应,打点就可以。
2.网站所有页面所有链接里有唯一的一个,那么此时也可以打个标记,打出该标记的,就可以认为是所有网站内容的主页,再对其进行提取查询即可。3.根据网站提取所需内容的属性,单独查询每个页面的链接,其内容只有指定页面的内容,提取查询即可。4.找到唯一符合需求的页面,也就是在列表页中的搜索框中,输入关键词后,你可以找到搜索内容,根据页面的内容来匹配内容,就可以得到所有网站内容的索引号。
5.从该页面的链接中提取,确定该页面具体是哪个搜索内容的页面,在列表页内搜索关键词后,可以找到该页面的内容,匹配页面内容(指定页面内容),就可以找到所有网站内容的索引号。6.提取完全需要的页面内容后,再根据实际需求进行查询即可。7.利用google的关键词功能,提取出关键词出来,就可以写一个spider进行搜索,一般网站如果内容很多很杂,且每个页面都需要内容,就会分两个号进行爬取,一个搜索关键词,一个搜索内容(即spider),爬取方式可以是每次爬取一个页面,查找后进行二次搜索。
备注:webspider在爬取的时候,会对全站爬取,因此不建议采用第一种方式,目前常用一种方式就是提取单个页面的链接后,再通过打点找到对应的指定页面内容,再提取出来,这样做的好处在于每个网站内容的种类不同,自然查找的内容也会有差异,也不太会造成流量的损失,相对来说,这种方式比较安全。webspider一般是通过谷歌搜索,搜索一些大词或者比较火的词,注意一定是要大词,不能通过一些比较冷的词,小词来搜索。
再根据页面的内容再进行分析,提取出指定网站内容。如果是爬取某些大站的内容,比如百度首页内容,可以先通过谷歌网站快照找到关键词,例如:某个站点上这个关键词,排在前50位的是谁,等,然后就是在页面后面添加关键词,例如:“”,页面抓取后,通过分析每个页面的内容,提取出需要的内容,如果是想对某一个网站页面内容或者整个网站内容进行抓取,那么就要对该网站的信息分析,清楚的知道这个网站的所有页面内容,并在。
JavaScript快速提取子域名URL工具
网站优化 • 优采云 发表了文章 • 0 个评论 • 56 次浏览 • 2022-06-04 12:36
0x01 JSFinder介绍
JSFinder 是一种用于从网站上的 JS 文件中快速提取 URL 和子域的工具,提取URL的正则部分使用的是LinkFinder项目。
增加油猴脚本用于在浏览器上访问页面时获取域名与接口,具体可见:
https://github.com/Threezh1/De ... inder
JSFinder获取URL和子域名的方式:
0x02JSFinder使用
python JSFinder.py -u http://xxxx.com
这个命令会爬取这个别页面的所有js链接,并在其中发现url和子域名
python JSFinder.py -u http://www.xxxx.com -d
深入一层页面爬取JS,时间会消耗时间,建议使用-ou和-os来指定保存URL和子域名的文件名。
python JSFinder.py -u http://www.xxxx.com -d -ou mi_url.txt -os mi_subdomain.txt
指定网址:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不用加深度爬取,一页图表。
-c 指定cookie来爬取页面示例:
python JSFinder.py -u http://www.xxxx.com -c "session=xxx"
-ou 指定文件名保存URL链接示例:
python JSFinder.py -u http://www.xxxx.com -ou XX_url.txt
-os 指定文件名保存子域名示例:
python JSFinder.py -u http://www.xxxx.com -os mi_subdomain.txt
0x03JSFinder获取 查看全部
JavaScript快速提取子域名URL工具
0x01 JSFinder介绍
JSFinder 是一种用于从网站上的 JS 文件中快速提取 URL 和子域的工具,提取URL的正则部分使用的是LinkFinder项目。
增加油猴脚本用于在浏览器上访问页面时获取域名与接口,具体可见:
https://github.com/Threezh1/De ... inder
JSFinder获取URL和子域名的方式:
0x02JSFinder使用
python JSFinder.py -u http://xxxx.com
这个命令会爬取这个别页面的所有js链接,并在其中发现url和子域名
python JSFinder.py -u http://www.xxxx.com -d
深入一层页面爬取JS,时间会消耗时间,建议使用-ou和-os来指定保存URL和子域名的文件名。
python JSFinder.py -u http://www.xxxx.com -d -ou mi_url.txt -os mi_subdomain.txt
指定网址:
python JSFinder.py -f text.txt
指定JS:
python JSFinder.py -f text.txt -j
可以用brupsuite爬取网站后提取出URL或者JS链接,保存到txt文件中,一行一个。
指定URL或JS就不用加深度爬取,一页图表。
-c 指定cookie来爬取页面示例:
python JSFinder.py -u http://www.xxxx.com -c "session=xxx"
-ou 指定文件名保存URL链接示例:
python JSFinder.py -u http://www.xxxx.com -ou XX_url.txt
-os 指定文件名保存子域名示例:
python JSFinder.py -u http://www.xxxx.com -os mi_subdomain.txt
0x03JSFinder获取
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 62 次浏览 • 2022-05-29 20:31
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
js提取指定网站内容 谈谈对vitejs预构建的理解
网站优化 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-05-28 00:21
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }} 查看全部
js提取指定网站内容 谈谈对vitejs预构建的理解
vite在官网介绍中,第一条就提到的特性就是自己的本地冷启动极快。这主要是得益于它在本地服务启动的时候做了预构建。出于好奇,抽时间了解了下vite在预构建部分的主要实现思路,分享出来供大家参考。
为啥要预构建
简单来讲就是为了提高本地开发服务器的冷启动速度。按照vite的说法,当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。随着应用规模的增大,打包速度显著下降,本地服务器的启动速度也跟着变慢。
为了加快本地开发服务器的启动速度,vite引入了预构建机制。在预构建工具的选择上,vite选择了esbuild。esbuild使用Go编写,比以JavaScript编写的打包器构建速度快 10-100 倍,有了预构建,再利用浏览器的esm方式按需加载业务代码,动态实时进行构建,结合缓存机制,大大提升了服务器的启动速度。
预构建的流程1. 查找依赖
如果是首次启动本地服务,那么vite会自动抓取源代码,从代码中找到需要预构建的依赖,最终对外返回类似下面的一个deps对象:
{ vue: '/path/to/your/project/node_modules/vue/dist/vue.runtime.esm-bundler.js', 'element-plus': '/path/to/your/project/node_modules/element-plus/es/index.mjs', 'vue-router': '/path/to/your/project/node_modules/vue-router/dist/vue-router.esm-bundler.js'}
具体实现就是,调用esbuild的buildapi,以index.html作为查找入口(entryPoints),将所有的来自node_modules以及在配置文件的optimizeDeps.include选项中指定的模块找出来。
//...省略其他代码 if (explicitEntryPatterns) { entries = await globEntries(explicitEntryPatterns, config) } else if (buildInput) { const resolvePath = (p: string) => path.resolve(config.root, p) if (typeof buildInput === 'string') { entries = [resolvePath(buildInput)] } else if (Array.isArray(buildInput)) { entries = buildInput.map(resolvePath) } else if (isObject(buildInput)) { entries = Object.values(buildInput).map(resolvePath) } else { throw new Error('invalid rollupOptions.input value.') } } else { // 重点看这里:使用html文件作为查找入口 entries = await globEntries('**/*.html', config) } //...省略其他代码build.onResolve( { // avoid matching windows volume filter: /^[\w@][^:]/ }, async ({ path: id, importer }) => { const resolved = await resolve(id, importer) if (resolved) { // 来自node_modules和在include中指定的模块 if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling<br /> if (isOptimizable(resolved)) { // 重点看这里:将符合预构建条件的依赖记录下来,depImports就是对外导出的需要预构建的依赖对象 depImports[id] = resolved } return externalUnlessEntry({ path: id }) } else if (isScannable(resolved)) { const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined // linked package, keep crawling return { path: path.resolve(resolved), namespace } } else { return externalUnlessEntry({ path: id }) } } else { missing[id] = normalizePath(importer) } } )
但是熟悉esbuild的小伙伴可能知道,esbuild默认支持的入口文件类型有js、ts、jsx、css、json、base64、dataurl、binary、file(.png等),并不包括html。vite是如何做到将index.html作为打包入口的呢?原因是vite自己实现了一个esbuild插件esbuildScanPlugin,来处理.vue和.html这种类型的文件。具体做法是读取html的内容,然后将里面的script提取到一个esm格式的js模块。
// 对于html类型(.VUE/.HTML/.svelte等)的文件,提取文件里的script内容。html types: extract script contents ----------------------------------- build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => { const resolved = await resolve(path, importer) if (!resolved) return // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. if (resolved.includes('node_modules') && isOptimizable(resolved)) return return { path: resolved, namespace: 'html' } })<br /> // 配合build.onResolve,对于类html文件,提取其中的script,作为一个js模块extract scripts inside HTML-like files and treat it as a js module build.onLoad( { filter: htmlTypesRE, namespace: 'html' }, async ({ path }) => { let raw = fs.readFileSync(path, 'utf-8') // Avoid matching the content of the comment raw = raw.replace(commentRE, '') const isHtml = path.endsWith('.html') const regex = isHtml ? scriptModuleRE : scriptRE regex.lastIndex = 0 // js 的内容被处理成了一个虚拟模块 let js = '' let scriptId = 0 let match: RegExpExecArray | null while ((match = regex.exec(raw))) { const [, openTag, content] = match const typeMatch = openTag.match(typeRE) const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3]) const langMatch = openTag.match(langRE) const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3]) // skip type="application/ld+json" and other non-JS types if ( type && !( type.includes('javascript') || type.includes('ecmascript') || type === 'module' ) ) { continue } // 默认的js文件的loader是js,其他对于ts、tsx jsx有对应的同名loader let loader: Loader = 'js' if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') { loader = lang } const srcMatch = openTag.match(srcRE) // 对于引入的js,将它转换为import 'path/to/some.js'的代码 if (srcMatch) { const src = srcMatch[1] || srcMatch[2] || srcMatch[3] js += `import ${JSON.stringify(src)}\n` } else if (content.trim()) { // The reason why virtual modules are needed: // 1. There can be module scripts (`` in Svelte and `` in Vue) // or local scripts (`` in Svelte and `` in Vue) // 2. There can be multiple module scripts in html // We need to handle these separately in case variable names are reused between them<br /> // append imports in TS to prevent esbuild from removing them // since they may be used in the template const contents = content + (loader.startsWith('ts') ? extractImportPaths(content) : '')<br /> // 将提取出来的script脚本,存在以xx.vue?id=1为key的script对象中script={'xx.vue?id=1': 'js contents'} const key = `${path}?id=${scriptId++}`<br /> if (contents.includes('import.meta.glob')) { scripts[key] = { // transformGlob already transforms to js loader: 'js', contents: await transformGlob( contents, path, config.root, loader, resolve, config.logger ) } } else { scripts[key] = { loader, contents } }<br /> const virtualModulePath = JSON.stringify( virtualModulePrefix + key )<br /> const contextMatch = openTag.match(contextRE) const context = contextMatch && (contextMatch[1] || contextMatch[2] || contextMatch[3])<br /> // Especially for Svelte files, exports in means module exports, // exports in means component props. To avoid having two same export name from the // star exports, we need to ignore exports in if (path.endsWith('.svelte') && context !== 'module') { js += `import ${virtualModulePath}\n` } else { // e.g. export * from 'virtual-module:xx.vue?id=1' js += `export * from ${virtualModulePath}\n` } } }<br /> // This will trigger incorrectly if `export default` is contained // anywhere in a string. Svelte and Astro files can't have // `export default` as code so we know if it's encountered it's a // false positive (e.g. contained in a string) if (!path.endsWith('.vue') || !js.includes('export default')) { js += '\nexport default {}' }<br /> return { loader: 'js', contents: js } } )
由上文我们可知,来自node_modules中的模块依赖是需要预构建的。例如import ElementPlus from 'element-plus'。因为在浏览器环境下,是不支持这种裸模块引用的(bare import)。另一方面,如果不进行构建,浏览器面对由成百上千的子模块组成的依赖,依靠原生esm的加载机制,每个的依赖的import都将产生一次http请求。面对大量的请求,浏览器是吃不消的。因此客观上需要对裸模块引入进行打包,并处理成浏览器环境下支持的相对路径或路径的导入方式。例如:import ElementPlus from '/path/to/.vite/element-plus/es/index.mjs'。
2. 对查找到的依赖进行构建
在上一步,已经得到了需要预构建的依赖列表。现在需要把他们作为esbuild的entryPoints打包就行了。
//使用esbuild打包,入口文件即为第一步中抓取到的需要预构建的依赖 import { build } from 'esbuild' // ...省略其他代码 const result = await build({ absWorkingDir: process.cwd(), // flatIdDeps即为第一步中所得到的需要预构建的依赖对象 entryPoints: Object.keys(flatIdDeps), bundle: true, format: 'esm', target: config.build.target || undefined, external: config.optimizeDeps?.exclude, logLevel: 'error', splitting: true, sourcemap: true,// outdir指定打包产物输出目录,processingCacheDir这里并不是.vite,而是存放构建产物的临时目录 outdir: processingCacheDir, ignoreAnnotations: true, metafile: true, define, plugins: [ ...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr) ], ...esbuildOptions })<br /> // 写入_metadata文件,并替换缓存文件。Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync commitProcessingDepsCacheSync()
vite并没有将esbuild的outdir(构建产物的输出目录)直接配置为.vite目录,而是先将构建产物存放到了一个临时目录。当构建完成后,才将原来旧的.vite(如果有的话)删除。然后再将临时目录重命名为.vite。这样做主要是为了避免在程序运行过程中发生了错误,导致缓存不可用。
function commitProcessingDepsCacheSync() { // Rewire the file paths from the temporal processing dir to the final deps cache dir const dataPath = path.join(processingCacheDir, '_metadata.json') writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata)) // Processing is done, we can now replace the depsCacheDir with processingCacheDir // 依赖处理完成后,使用依赖缓存目录替换处理中的依赖缓存目录 if (fs.existsSync(depsCacheDir)) { const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped rmSync(depsCacheDir, { recursive: true }) } fs.renameSync(processingCacheDir, depsCacheDir) }}
JavaScript数组常用方法解析和深层次js数组扁平化
网站优化 • 优采云 发表了文章 • 0 个评论 • 75 次浏览 • 2022-05-26 16:22
JavaScript数组常用方法解析和深层次js数组扁平化前言
数组作为在开发中常用的集合,除了for循环遍历以外,还有很多内置对象的方法,包括map,以及数组筛选元素filter等。
注:文章结尾处附深层次数组扁平化方法操作。
作为引用数据类型的一种,在处理数组Array的时候,我们需要考虑到深拷贝和浅拷贝的情况可以参考以下文章
常用数组操作方法push末尾追加元素
/**<br /> * @param push 将一个或多个元素添加到数组的末尾,返回该数组的新长度<br /> *<br /> * 集合apply和call合并数组<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.push("xiaoming")); // 3<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br />let user1 = ["xiaowang", "xiaoming"];<br />let user2 = ["zhangsan", "lisi"];<br />console.log(Array.prototype.push.apply(user1, user2)); // 4<br />console.log(user1); // ["xiaowang", "xiaoming", "zhangsan", "lisi"]<br /><br />
pop删除数组末尾元素
/**<br /> *<br /> * @param pop 方法从数组中删除最后一个元素,返回值是该元素。<br /> *<br /> * 如果数组是空数组,那么返回的是undefined<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.pop()); // lisi<br />console.log(user); // ["zhangsan"]<br />let empArray = [];<br />console.log(empArray.pop()); // undefined<br /><br />
sort排序
/**<br /> *<br /> * @param sort<br /> *<br /> * 使用原地算法对数组的元素进行排序,并返回数组。<br /> * 默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的<br /> * 由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。<br /> *<br /> * arr.sort([compareFunction])<br /> *<br /> * @param compareFunction<br /> *<br /> * 用来指定按某种顺序进行排列的函数。<br /> * 如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。<br /> *<br /> * 如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。<br /> * 例如 "Banana" 会被排列到 "cherry" 之前。<br /> * 当数字按由小到大排序时,9 出 * 现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。<br /> * 如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:<br /> * 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;<br /> * 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。<br /> * 备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);<br /> * 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。<br /> * compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。<br /> *<br /> * firstEl<br /> * 第一个用于比较的元素。<br /> * secondEl<br /> * 第二个用于比较的元素<br /> *<br /> */<br />/**<br /> *<br /> * 基本用法<br /> *<br /> * */<br /><br />const user = ["zhangsan", "lisi", "xiaoming", "xiaowang"];<br />user.sort();<br />console.log(user); // ["lisi", "xiaoming", "xiaowang", "zhangsan"]<br />const array1 = [1, 30, 4, 21, 100000];<br />array1.sort();<br />console.log(array1); // [1, 100000, 21, 30, 4]<br /><br />/**<br /> *<br /> * 自定义排序方法<br /> *<br /> * */<br />var numbers = [4, 2, 5, 1, 3];<br />let sortFun = function (a, b) {<br /> return a - b;<br />};<br />numbers.sort(sortFun);<br />console.log(numbers); // [1, 2, 3, 4, 5]<br /><br />
shift数组开头添加元素 && unshift数组开头删除元素
/**<br /> *<br /> * @param shift<br /> * 从数组中删除第一个元素,并返回该元素的值,如果删除空数组,返回值是undefined<br /> *<br /> * @param unshift<br /> * 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度<br /> *<br /> * */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.shift()); // zhangsan<br />console.log(user); // ["lisi"]<br />let empArray = [];<br />console.log(empArray.shift()); // undefined<br />let user1 = ["xiaoming", "xiaowang"];<br />console.log(user1.unshift("xiaoming1", "xiaowang1")); // 4<br />console.log(user1); // ["xiaoming1", "xiaowang1", "xiaoming", "xiaowang"]<br /><br />
数组合并concat
<br />/**<br /> *<br /> * @param concat<br /> *<br /> * 方法用于合并两个或多个数组。返回值是新数组,原数组不会发生更改<br /> *<br /> * 注:数组合并是浅拷贝<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />let user1 = [["xiaowang"], { name: "xiaoming" }];<br />console.log(user.concat(user1)); // ["zhangsan","lisi",["xiaowang"],{name: "xiaoming"}]<br />console.log(user); // ["zhangsan", "lisi"]<br /><br />
indexOf查找元素 && includes查找元素是否存在
/**<br /> *<br /> * @param indexOf<br /> *<br /> * 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1,<br /> * 常用于判断数组是否存在某个元素<br /> *<br /> * @param includes<br /> *<br /> * 判断一个数组是否包含一个指定的值,返回值是布尔值 true 或者 false<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.indexOf("lisi")); // 1<br />console.log(user.indexOf("xiaoming")); // -1<br />let user1 = ["zhangsan", ["xiaowang"], { name: "xiaoming" }];<br />console.log(user1.includes("zhangsan")); // true<br />console.log(user1.includes(["xiaowang"])); // false<br />console.log(user1.includes({ name: "xiaoming" })); // false<br /><br />
reverse反转数组
/**<br /> *<br /> * @param reverse<br /> *<br /> * 反转数组元素,将原有数组倒叙显示,会改变元素的元素位置<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.reverse()); // ["xiaoming", "lisi", "zhangsan"]<br />console.log(user); // ["xiaoming", "lisi", "zhangsan"]<br />let user1 = ["zhangsan", ["xiaowang", "lisi"], { name: "xiaoming" }];<br />console.log(user1.reverse()); // [{name: "xiaoming"},["xiaowang", "lisi"],"zhangsan"]<br /><br />
数组切割成字符串join
/**<br /> *<br /> * @param join<br /> *<br /> * 根据传入的参数字符串,对数组进行切割,返回值是使用参数拼接元素的字符串<br /> * 如果数组只有一个元素,则不使用分割符号<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.join(" ")); // zhangsan lisi xiaoming<br />console.log(user.join("")); // zhangsanlisixiaoming<br />console.log(user.join(",")); // zhangsan,lisi,xiaoming<br />console.log(user.join({ a: 1 })); // zhangsan[object Object]lisi[object Object]xiaoming<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br /><br />
slice操作数组,替换,删除,新增
slice使用的范围较广,不同的参数可以实现对数组的删除,新增和替换等,使用的时候需要注意参数的具体使用方法
/**<br /> *<br /> * @param slice<br /> *<br /> * 返回一个新的数组对象,<br /> * 这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。<br /> *<br /> * @param begin<br /> * 提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。<br /> * 如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。<br /> * 如果省略 begin,则 slice 从索引 0 开始。<br /> * 如果 begin 超出原数组的索引范围,则会返回空数组<br /> *<br /> * @param end<br /> *<br /> * 提取终止处的索引(从 0 开始),在该索引处结束提取原数组元素。<br /> * slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。<br /> * slice(1,4) 会提取原数组中从第二个元素开始一直到第四个元素的所有元素 (索引为 1, 2, 3的元素)。<br /> * 如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。<br /> * slice(-2,-1) 表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。<br /> * 如果 end 被省略,则 slice 会一直提取到原数组末尾。如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。<br /> *<br /> */<br />const animals = ["ant", "bison", "camel", "duck", "elephant"];<br />console.log(animals.slice(2)); // Array ["camel", "duck", "elephant"]<br />console.log(animals.slice(2, 4)); // Array ["camel", "duck"]<br />console.log(animals.slice(1, 5)); // Array ["bison", "camel", "duck", "elephant"]<br />console.log(animals.slice(-2)); // Array ["duck", "elephant"]<br />console.log(animals.slice(2, -1)); // Array ["camel", "duck"]<br />console.log(animals.slice()); // Array ["ant", "bison", "camel", "duck", "elephant"]<br />/**<br /> *<br /> * @param splice(start[, deleteCount[, item1[, item2[, ...]]]])<br /> *<br /> * 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组<br /> *<br /> * 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。<br /> *<br /> * @param start<br /> *<br /> * 指定修改的开始位置,默认从下标0开始。<br /> * 如果超出了数组的长度,则从数组末尾开始添加元素;<br /> * 如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);<br /> * 如果负数的绝对值大于数组的长度,则表示开始位置为第0位。<br /> *<br /> * @param deleteCount<br /> *<br /> * 整数,表示要移除的数组元素的个数。<br /> * 如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。<br /> * 如果 deleteCount 被省略了,<br /> * 或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),<br /> * 那么start之后数组的所有元素都会被删除。<br /> *<br /> * 如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。<br /> * @param item1, item2, ...<br /> *<br /> * 要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素<br /> *<br /> */<br />const months = ["Jan", "March", "April", "June"];<br />months.splice(1, 0, "Feb"); // 下表为1,插入一个元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "June"]<br />months.splice(4, 1, "May"); // 替换下标为4的元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "May"]<br />let del = months.splice(1, 1); // 删除<br />console.log(del); // ["Feb"]<br />console.log(months); // ["Jan", "April", "May"]<br /><br />
every校验数组所有元素
/**<br /> *<br /> * @param every<br /> * 测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回值是布尔值 true or false<br /> * 备注:若收到一个空数组,此方法在任何情况下都会返回 true。<br /> *<br /> * arr.every(callback(element[, index[, array]])[, thisArg])<br /> * callback<br /> * 用来测试每个元素的函数,它可以接收三个参数:<br /> *<br /> * @param element 用于测试的当前值。<br /> * @param index可选 用于测试的当前值的索引。<br /> * @param array可选 调用 every 的当前数组。<br /> *<br /> * every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 false 的元素。<br /> * 如果发现了一个这样的元素,every 方法将会立即返回 false。<br /> * 否则,callback 为每一个元素返回 true,every 就会返回 true。<br /> *<br /> * callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从未被赋值的索引调用。<br /> * callback 在被调用时可传入三个参数:元素值,元素的索引,原数组。<br /> * 如果为 every 提供一个 thisArg 参数,则该参数为调用 callback 时的 this 值。<br /> * 如果省略该参数,则 callback 被调用时的 this 值,在非严格模式下为全局对象,在严格模式下传入 undefined。<br /> *<br /> *<br /> * every 不会改变原数组。<br /> * every 遍历的元素范围在第一次调用 callback 之前就已确定了。<br /> * 在调用 every 之后添加到数组中的元素不会被 callback 访问到。<br /> * 如果数组中存在的元素被更改,则他们传入 callback 的值是 every 访问到他们那一刻的值。<br /> * 那些被删除的元素或从来未被赋值的元素将不会被访问到。<br /> *<br /> * */<br />const isBelowThreshold = (currentValue) => currentValue element % 2 === 0; //确认偶数<br />console.log(array.some(even)); // true;<br /><br />
深层次递归数组flat
<br />/**<br /> * @param flat 按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。<br /> *<br /> * var newArray = arr.flat([depth])<br /> * @depth 指定要提取嵌套数组的结构深度,默认值为 1。<br /> * */<br />let arr1 = [1, 2, [3, 4]];<br />console.log(arr1.flat()); // [1, 2, 3, 4]<br />let arr2 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]]<br />let arr3 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr3.flat(2)); // [1, 2, 3, 4, 5, 6]<br />//使用 Infinity,可展开任意深度的嵌套数组<br />let arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];<br />console.log(arr4.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]<br />let objArray = [{ name: "zhangsan", children: ["张三"] }];<br />console.log(objArray.flat(Infinity)); // [{ name: "zhangsan", children: ["张三"] }]<br /><br />
map遍历数组
/**<br /> * @param map<br /> *<br /> * 创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成<br /> *<br /> * */<br />const array1 = [1, 4, 9, 16];<br />const map1 = array1.map((x) => x * 2);<br />console.log(map1); // [2, 8, 18, 32]<br /><br />
reduce和filter
reduce和filter的基本操作方法在之前的文章有提到过,这里不做复述
文章地址:JavaScript 数组方法filter和reduce
数组操作示例:数组对象根据属性整理数组
/**<br /> * 按照数组对象某个属性整理数据<br /> *<br /> * */<br />let user1 = [<br /> { name: "zhangsan", age: 21 },<br /> { name: "lisi", age: 20 },<br /> { name: "xiaoming", age: 20 },<br />];<br />function groupBy(objectArray, property) {<br /> return objectArray.reduce(function (acc, obj) {<br /> let key = obj[property];<br /> if (!acc[key]) {<br /> acc[key] = [];<br /> }<br /> acc[key].push(obj);<br /> return acc;<br /> }, {});<br />}<br />let ageList = groupBy(user1, "age");<br />console.log(ageList); // {[{name: "lisi", age: 20},{name: "xiaoming", age: 20}],[{name: "zhangsan", age: 21}]}<br /><br /><br />
数组扁平化-深层次
function flatten(array) {<br /> var flattend = [];<br /> (function flat(array) {<br /> array.forEach(function (el) {<br /> for (let i in el) {<br /> if (Object.prototype.toString.call(el[i]) === "[object Array]")<br /> flat(el[i]);<br /> }<br /> flattend.push(el);<br /> });<br /> })(array);<br /> return flattend;<br />}<br />let user2 = [<br /> {<br /> name: "zhangsan",<br /> age: 20,<br /> child: [{ name: "xiaoming" }],<br /> child1: [{ name: "xiaowang" }],<br /> },<br />];<br />let flattenArray = flatten(user2);<br />console.log(flattenArray);<br /><br />
结尾
以上就是JavaScript中数组较为常用的方法,其他没有提及的方法,需要的同学可以查阅相关文章,或者留言,后续的文章整理然后作为补充。
源码地址 查看全部
JavaScript数组常用方法解析和深层次js数组扁平化
JavaScript数组常用方法解析和深层次js数组扁平化前言
数组作为在开发中常用的集合,除了for循环遍历以外,还有很多内置对象的方法,包括map,以及数组筛选元素filter等。
注:文章结尾处附深层次数组扁平化方法操作。
作为引用数据类型的一种,在处理数组Array的时候,我们需要考虑到深拷贝和浅拷贝的情况可以参考以下文章
常用数组操作方法push末尾追加元素
/**<br /> * @param push 将一个或多个元素添加到数组的末尾,返回该数组的新长度<br /> *<br /> * 集合apply和call合并数组<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.push("xiaoming")); // 3<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br />let user1 = ["xiaowang", "xiaoming"];<br />let user2 = ["zhangsan", "lisi"];<br />console.log(Array.prototype.push.apply(user1, user2)); // 4<br />console.log(user1); // ["xiaowang", "xiaoming", "zhangsan", "lisi"]<br /><br />
pop删除数组末尾元素
/**<br /> *<br /> * @param pop 方法从数组中删除最后一个元素,返回值是该元素。<br /> *<br /> * 如果数组是空数组,那么返回的是undefined<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.pop()); // lisi<br />console.log(user); // ["zhangsan"]<br />let empArray = [];<br />console.log(empArray.pop()); // undefined<br /><br />
sort排序
/**<br /> *<br /> * @param sort<br /> *<br /> * 使用原地算法对数组的元素进行排序,并返回数组。<br /> * 默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的<br /> * 由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。<br /> *<br /> * arr.sort([compareFunction])<br /> *<br /> * @param compareFunction<br /> *<br /> * 用来指定按某种顺序进行排列的函数。<br /> * 如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。<br /> *<br /> * 如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。<br /> * 例如 "Banana" 会被排列到 "cherry" 之前。<br /> * 当数字按由小到大排序时,9 出 * 现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。<br /> * 如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:<br /> * 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;<br /> * 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。<br /> * 备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);<br /> * 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。<br /> * compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。<br /> *<br /> * firstEl<br /> * 第一个用于比较的元素。<br /> * secondEl<br /> * 第二个用于比较的元素<br /> *<br /> */<br />/**<br /> *<br /> * 基本用法<br /> *<br /> * */<br /><br />const user = ["zhangsan", "lisi", "xiaoming", "xiaowang"];<br />user.sort();<br />console.log(user); // ["lisi", "xiaoming", "xiaowang", "zhangsan"]<br />const array1 = [1, 30, 4, 21, 100000];<br />array1.sort();<br />console.log(array1); // [1, 100000, 21, 30, 4]<br /><br />/**<br /> *<br /> * 自定义排序方法<br /> *<br /> * */<br />var numbers = [4, 2, 5, 1, 3];<br />let sortFun = function (a, b) {<br /> return a - b;<br />};<br />numbers.sort(sortFun);<br />console.log(numbers); // [1, 2, 3, 4, 5]<br /><br />
shift数组开头添加元素 && unshift数组开头删除元素
/**<br /> *<br /> * @param shift<br /> * 从数组中删除第一个元素,并返回该元素的值,如果删除空数组,返回值是undefined<br /> *<br /> * @param unshift<br /> * 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度<br /> *<br /> * */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.shift()); // zhangsan<br />console.log(user); // ["lisi"]<br />let empArray = [];<br />console.log(empArray.shift()); // undefined<br />let user1 = ["xiaoming", "xiaowang"];<br />console.log(user1.unshift("xiaoming1", "xiaowang1")); // 4<br />console.log(user1); // ["xiaoming1", "xiaowang1", "xiaoming", "xiaowang"]<br /><br />
数组合并concat
<br />/**<br /> *<br /> * @param concat<br /> *<br /> * 方法用于合并两个或多个数组。返回值是新数组,原数组不会发生更改<br /> *<br /> * 注:数组合并是浅拷贝<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />let user1 = [["xiaowang"], { name: "xiaoming" }];<br />console.log(user.concat(user1)); // ["zhangsan","lisi",["xiaowang"],{name: "xiaoming"}]<br />console.log(user); // ["zhangsan", "lisi"]<br /><br />
indexOf查找元素 && includes查找元素是否存在
/**<br /> *<br /> * @param indexOf<br /> *<br /> * 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1,<br /> * 常用于判断数组是否存在某个元素<br /> *<br /> * @param includes<br /> *<br /> * 判断一个数组是否包含一个指定的值,返回值是布尔值 true 或者 false<br /> *<br /> */<br />let user = ["zhangsan", "lisi"];<br />console.log(user.indexOf("lisi")); // 1<br />console.log(user.indexOf("xiaoming")); // -1<br />let user1 = ["zhangsan", ["xiaowang"], { name: "xiaoming" }];<br />console.log(user1.includes("zhangsan")); // true<br />console.log(user1.includes(["xiaowang"])); // false<br />console.log(user1.includes({ name: "xiaoming" })); // false<br /><br />
reverse反转数组
/**<br /> *<br /> * @param reverse<br /> *<br /> * 反转数组元素,将原有数组倒叙显示,会改变元素的元素位置<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.reverse()); // ["xiaoming", "lisi", "zhangsan"]<br />console.log(user); // ["xiaoming", "lisi", "zhangsan"]<br />let user1 = ["zhangsan", ["xiaowang", "lisi"], { name: "xiaoming" }];<br />console.log(user1.reverse()); // [{name: "xiaoming"},["xiaowang", "lisi"],"zhangsan"]<br /><br />
数组切割成字符串join
/**<br /> *<br /> * @param join<br /> *<br /> * 根据传入的参数字符串,对数组进行切割,返回值是使用参数拼接元素的字符串<br /> * 如果数组只有一个元素,则不使用分割符号<br /> *<br /> */<br />let user = ["zhangsan", "lisi", "xiaoming"];<br />console.log(user.join(" ")); // zhangsan lisi xiaoming<br />console.log(user.join("")); // zhangsanlisixiaoming<br />console.log(user.join(",")); // zhangsan,lisi,xiaoming<br />console.log(user.join({ a: 1 })); // zhangsan[object Object]lisi[object Object]xiaoming<br />console.log(user); // ["zhangsan", "lisi", "xiaoming"]<br /><br />
slice操作数组,替换,删除,新增
slice使用的范围较广,不同的参数可以实现对数组的删除,新增和替换等,使用的时候需要注意参数的具体使用方法
/**<br /> *<br /> * @param slice<br /> *<br /> * 返回一个新的数组对象,<br /> * 这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。<br /> *<br /> * @param begin<br /> * 提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。<br /> * 如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。<br /> * 如果省略 begin,则 slice 从索引 0 开始。<br /> * 如果 begin 超出原数组的索引范围,则会返回空数组<br /> *<br /> * @param end<br /> *<br /> * 提取终止处的索引(从 0 开始),在该索引处结束提取原数组元素。<br /> * slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。<br /> * slice(1,4) 会提取原数组中从第二个元素开始一直到第四个元素的所有元素 (索引为 1, 2, 3的元素)。<br /> * 如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。<br /> * slice(-2,-1) 表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。<br /> * 如果 end 被省略,则 slice 会一直提取到原数组末尾。如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。<br /> *<br /> */<br />const animals = ["ant", "bison", "camel", "duck", "elephant"];<br />console.log(animals.slice(2)); // Array ["camel", "duck", "elephant"]<br />console.log(animals.slice(2, 4)); // Array ["camel", "duck"]<br />console.log(animals.slice(1, 5)); // Array ["bison", "camel", "duck", "elephant"]<br />console.log(animals.slice(-2)); // Array ["duck", "elephant"]<br />console.log(animals.slice(2, -1)); // Array ["camel", "duck"]<br />console.log(animals.slice()); // Array ["ant", "bison", "camel", "duck", "elephant"]<br />/**<br /> *<br /> * @param splice(start[, deleteCount[, item1[, item2[, ...]]]])<br /> *<br /> * 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组<br /> *<br /> * 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。<br /> *<br /> * @param start<br /> *<br /> * 指定修改的开始位置,默认从下标0开始。<br /> * 如果超出了数组的长度,则从数组末尾开始添加元素;<br /> * 如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);<br /> * 如果负数的绝对值大于数组的长度,则表示开始位置为第0位。<br /> *<br /> * @param deleteCount<br /> *<br /> * 整数,表示要移除的数组元素的个数。<br /> * 如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。<br /> * 如果 deleteCount 被省略了,<br /> * 或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),<br /> * 那么start之后数组的所有元素都会被删除。<br /> *<br /> * 如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。<br /> * @param item1, item2, ...<br /> *<br /> * 要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素<br /> *<br /> */<br />const months = ["Jan", "March", "April", "June"];<br />months.splice(1, 0, "Feb"); // 下表为1,插入一个元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "June"]<br />months.splice(4, 1, "May"); // 替换下标为4的元素<br />console.log(months); // ["Jan", "Feb", "March", "April", "May"]<br />let del = months.splice(1, 1); // 删除<br />console.log(del); // ["Feb"]<br />console.log(months); // ["Jan", "April", "May"]<br /><br />
every校验数组所有元素
/**<br /> *<br /> * @param every<br /> * 测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回值是布尔值 true or false<br /> * 备注:若收到一个空数组,此方法在任何情况下都会返回 true。<br /> *<br /> * arr.every(callback(element[, index[, array]])[, thisArg])<br /> * callback<br /> * 用来测试每个元素的函数,它可以接收三个参数:<br /> *<br /> * @param element 用于测试的当前值。<br /> * @param index可选 用于测试的当前值的索引。<br /> * @param array可选 调用 every 的当前数组。<br /> *<br /> * every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个会使 callback 返回 false 的元素。<br /> * 如果发现了一个这样的元素,every 方法将会立即返回 false。<br /> * 否则,callback 为每一个元素返回 true,every 就会返回 true。<br /> *<br /> * callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从未被赋值的索引调用。<br /> * callback 在被调用时可传入三个参数:元素值,元素的索引,原数组。<br /> * 如果为 every 提供一个 thisArg 参数,则该参数为调用 callback 时的 this 值。<br /> * 如果省略该参数,则 callback 被调用时的 this 值,在非严格模式下为全局对象,在严格模式下传入 undefined。<br /> *<br /> *<br /> * every 不会改变原数组。<br /> * every 遍历的元素范围在第一次调用 callback 之前就已确定了。<br /> * 在调用 every 之后添加到数组中的元素不会被 callback 访问到。<br /> * 如果数组中存在的元素被更改,则他们传入 callback 的值是 every 访问到他们那一刻的值。<br /> * 那些被删除的元素或从来未被赋值的元素将不会被访问到。<br /> *<br /> * */<br />const isBelowThreshold = (currentValue) => currentValue element % 2 === 0; //确认偶数<br />console.log(array.some(even)); // true;<br /><br />
深层次递归数组flat
<br />/**<br /> * @param flat 按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。<br /> *<br /> * var newArray = arr.flat([depth])<br /> * @depth 指定要提取嵌套数组的结构深度,默认值为 1。<br /> * */<br />let arr1 = [1, 2, [3, 4]];<br />console.log(arr1.flat()); // [1, 2, 3, 4]<br />let arr2 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]]<br />let arr3 = [1, 2, [3, 4, [5, 6]]];<br />console.log(arr3.flat(2)); // [1, 2, 3, 4, 5, 6]<br />//使用 Infinity,可展开任意深度的嵌套数组<br />let arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];<br />console.log(arr4.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]<br />let objArray = [{ name: "zhangsan", children: ["张三"] }];<br />console.log(objArray.flat(Infinity)); // [{ name: "zhangsan", children: ["张三"] }]<br /><br />
map遍历数组
/**<br /> * @param map<br /> *<br /> * 创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成<br /> *<br /> * */<br />const array1 = [1, 4, 9, 16];<br />const map1 = array1.map((x) => x * 2);<br />console.log(map1); // [2, 8, 18, 32]<br /><br />
reduce和filter
reduce和filter的基本操作方法在之前的文章有提到过,这里不做复述
文章地址:JavaScript 数组方法filter和reduce
数组操作示例:数组对象根据属性整理数组
/**<br /> * 按照数组对象某个属性整理数据<br /> *<br /> * */<br />let user1 = [<br /> { name: "zhangsan", age: 21 },<br /> { name: "lisi", age: 20 },<br /> { name: "xiaoming", age: 20 },<br />];<br />function groupBy(objectArray, property) {<br /> return objectArray.reduce(function (acc, obj) {<br /> let key = obj[property];<br /> if (!acc[key]) {<br /> acc[key] = [];<br /> }<br /> acc[key].push(obj);<br /> return acc;<br /> }, {});<br />}<br />let ageList = groupBy(user1, "age");<br />console.log(ageList); // {[{name: "lisi", age: 20},{name: "xiaoming", age: 20}],[{name: "zhangsan", age: 21}]}<br /><br /><br />
数组扁平化-深层次
function flatten(array) {<br /> var flattend = [];<br /> (function flat(array) {<br /> array.forEach(function (el) {<br /> for (let i in el) {<br /> if (Object.prototype.toString.call(el[i]) === "[object Array]")<br /> flat(el[i]);<br /> }<br /> flattend.push(el);<br /> });<br /> })(array);<br /> return flattend;<br />}<br />let user2 = [<br /> {<br /> name: "zhangsan",<br /> age: 20,<br /> child: [{ name: "xiaoming" }],<br /> child1: [{ name: "xiaowang" }],<br /> },<br />];<br />let flattenArray = flatten(user2);<br />console.log(flattenArray);<br /><br />
结尾
以上就是JavaScript中数组较为常用的方法,其他没有提及的方法,需要的同学可以查阅相关文章,或者留言,后续的文章整理然后作为补充。
源码地址
js提取指定网站内容或页面内容为字符串js获取
网站优化 • 优采云 发表了文章 • 0 个评论 • 64 次浏览 • 2022-05-15 03:04
js提取指定网站内容或页面内容为字符串js获取包含某个或多个script标签的文件:使用javascriptscript-loaderscript-loader也可以通过javascriptscript-regex具体详见:
参考thegooglestyleguide
dom提取?不是很建议这么做,毕竟浏览器只支持预览。如果这么做用到了javascriptscript的话,那岂不是只是拼拼符号,不支持预览。按javascript的原理,想要提取文本,首先要声明一个class的变量,再创建一个script标签,随之进行编写自己想要的样式。根据自己的描述,猜测你应该是想生成的每个script标签中的某些字符串想要进行提取。
上面说的对,另外有个更优雅的方式:利用javascript语言特性,
javascript特有的语法,特殊的方法,无论是传统提取还是使用ajax提取,都是有一定优势的。先看一下传统提取方法传统的提取方法对于dom提取,目标是什么呢?script标签(不包括frame),通过中间嵌套一个dom,然后进行提取。这样一个标签中也许有多个内容,在这种情况下提取其中一个内容即可。
那么问题来了,因为传统提取方法的架构复杂,通常需要对dom进行初步分析,进而提取元素。其次因为中间嵌套多个dom,导致dom分析的过程异常麻烦。至于提取多个元素,对于嵌套中间的dom对象,dom还是同一个dom对象吗?是的,是一个dom对象。但是,同一个dom对象并不代表是唯一的。这是因为即使是script标签,它每个内容都可以是不同的,我们需要把它提取为同一个dom对象,然后组合提取多个。
ajax提取此方法对dom提取比传统方法优势极大,就是对dom提取起步简单许多。目标不是script标签,仅仅对dom进行提取即可。对于dom提取有一个缺点,那就是如果不用相应的中间层,单独写一个script中间标签,效率极低,整个工作量极大。而使用ajax提取,可以获取到dom,然后用javascript中间层提取提取多个。
而采用传统提取,必须是直接将dom提取出来放进dom对象进行组合操作,才能组合提取多个,然后整个工作量极大。基于以上,javascript提取相比传统提取优势非常明显。而且也能为后续优化提供基础。 查看全部
js提取指定网站内容或页面内容为字符串js获取
js提取指定网站内容或页面内容为字符串js获取包含某个或多个script标签的文件:使用javascriptscript-loaderscript-loader也可以通过javascriptscript-regex具体详见:
参考thegooglestyleguide
dom提取?不是很建议这么做,毕竟浏览器只支持预览。如果这么做用到了javascriptscript的话,那岂不是只是拼拼符号,不支持预览。按javascript的原理,想要提取文本,首先要声明一个class的变量,再创建一个script标签,随之进行编写自己想要的样式。根据自己的描述,猜测你应该是想生成的每个script标签中的某些字符串想要进行提取。
上面说的对,另外有个更优雅的方式:利用javascript语言特性,
javascript特有的语法,特殊的方法,无论是传统提取还是使用ajax提取,都是有一定优势的。先看一下传统提取方法传统的提取方法对于dom提取,目标是什么呢?script标签(不包括frame),通过中间嵌套一个dom,然后进行提取。这样一个标签中也许有多个内容,在这种情况下提取其中一个内容即可。
那么问题来了,因为传统提取方法的架构复杂,通常需要对dom进行初步分析,进而提取元素。其次因为中间嵌套多个dom,导致dom分析的过程异常麻烦。至于提取多个元素,对于嵌套中间的dom对象,dom还是同一个dom对象吗?是的,是一个dom对象。但是,同一个dom对象并不代表是唯一的。这是因为即使是script标签,它每个内容都可以是不同的,我们需要把它提取为同一个dom对象,然后组合提取多个。
ajax提取此方法对dom提取比传统方法优势极大,就是对dom提取起步简单许多。目标不是script标签,仅仅对dom进行提取即可。对于dom提取有一个缺点,那就是如果不用相应的中间层,单独写一个script中间标签,效率极低,整个工作量极大。而使用ajax提取,可以获取到dom,然后用javascript中间层提取提取多个。
而采用传统提取,必须是直接将dom提取出来放进dom对象进行组合操作,才能组合提取多个,然后整个工作量极大。基于以上,javascript提取相比传统提取优势非常明显。而且也能为后续优化提供基础。
js提取指定网站内容 关于Python的劝学指南
网站优化 • 优采云 发表了文章 • 0 个评论 • 47 次浏览 • 2022-05-01 15:49
关于Python的劝学指南
最近和几位朋友聊天,无意中谈到了关于Python的话题,这几位朋友之中有做财务的、有做投资的、也有项目负责人还有一些法务的朋友,他们所从事的行业与计算机没有太多关联,但是对Python所表现出来的热情,着实让我感到意外。他们选择学习Python的原因大致有两点:第一,认为Python很有用(有需求);第二,认为Python很简单(成本低,主要指时间成本)。那么,作为非计算机行业的从业人员,比如法律或者财税等其他非计算机行业的职业者,是否有学习这项技能的必要?这是我们今天要分享的话题。有言在先,分享这个话题,并不代表我本人的计算机水平达到了某种程度,只是作为一个业余学习Python的“过来人”,尝试对同样准备学习他的朋友们提供一些参考。对于专业选手来讲,本文的一些观点或许并不适用。
Python很有用?编程之路大致有两种途径,一种是先系统地学习编程语言,然后用语言解决遇到的问题;另一种是先有了应用场景需求,然后拆分问题寻找编程的实现方法。第一种属于被动接受的学习方式,也是传统教育普遍采用的培养方式,优点是基础往往比较牢靠,对知识也有全面的认知,缺点是学习的时间比较久,前期看不到成效并且不容易坚持下来。另一种属于主动探索的学习方式,通常是脱离学校教育后的主要学习方式,优点是目标和需求比较明确,能切实地解决问题,缺点也比较明显,没有系统化的指导和培养,往往需要自己寻找解决各种问题的方案,会走一些弯路,需要一点百折不挠的劲头才行。前者,适用于几乎所有的语言学习,后者适用于简单的语言学习,比如Python,也适用于从一门语言向另一门语言转变的学习,比如从C++转学Python。我是接触计算机比较早的一批人,起先学习的编程语言是C++,但是在取得该语言等级认证后的很长一段时间里,我并没有做出过任何一个实用的程序项目,因为如果不从事计算机行业,这些编程语言几乎没有任何应用场景,但Python算是个例外。我使用Python的第一个应用场景是毕业后的租房。当年的豆瓣网还是个很纯粹的平台,上海许多优质的房屋直租信息常常会在这个平台发布,对于有租房需求又没时间泡在平台上的我来讲,通过程序检索指定区域内的租房信息并将信息实时反馈给我,无疑是一个不错的方案,于是我向计算机专业的朋友请教了解决方案,他们向我推荐了Python(相比之下,这个场景用包括C语言在内的其他语言都很难实现),在见识到它强大的网络爬虫功能后,我开启了自己的Python之路。
律师的职业中,Python的应用场景主要围绕着文本文档处理和信息筛查展开,具体可以分为这样几类:(1)文本信息的筛查,尤其是对批量文本进行深度检索。比如,某年在某省高院协调处理地产纠纷诉讼事务的时候,我们对于解除房地产合作协议的诉讼案件收费标准与当地法院有了不同的看法,当地法院倾向于按照合同总价款的比例收取,而我们倾向于按照申请金额的比例收取,两个收费的标准相差了近600多万。为了佐证我们的观点,我们调取了北京、上海、广州等几个重要城市的近500余份裁判文书,并从中提取出诉讼费的裁判数据,形成了一份关于诉讼费收取的专项报告,其中对文书筛选的工作便是通过编程来完成的。(2)网站关键信息检索。比如,在一些官方网站检索功能受限的情况下,通过遍历网页的方式可以较准确地检索到官方公布的数据和文书(此处,忍住吐嘈某度在信息检索方面的商业化运作,以及某些行政类网站信息维护的质量,暂略去1000字)。又比如,在公报案例库还不健全的时候,通过自定义编程建立起自己的数据中心。(3)资料的收集、归纳和整理。比如,某年在研究某集团公司历年来的1100余件重大诉讼案件的过程中,提取历年来的案件信息,汇总整理报告的部分,也是通过编程辅助完成的。
又比如,在企业并购的业务中,需要对某上市公司历年公报信息进行收集,正常情况下历年公布的信息需要逐一下载,这项工作同样也可以通过程序自动处理。(4)格式化文本及文档。包括PDF及OFFICE格式文档操作,格式转换,信息提取与变更等,通常可以很方便地实现多任务的串联操作。对于想要接触Python的朋友,建议大家认真地了解一下编程与自身应用场景需求之间的匹配问题,如果有好的软件可以方便地实现这些应用场景,其实并不需要自行编程处理。比如,一些课程机构宣扬的用Python操作Excel,就我个人的体验来看,其中很多应用场景并不如使用Office办公软件方便,倒是财会的朋友常用的VBA功能可以尝试使用Python作为替换。技术发展至今,编程所能够解决的问题仍然局限于批量的、规律的以及重复性的一些工作,或者把一些分散的独立任务串联到一起,这是编程所能实现的基本任务。如果想要应对那些少量的、不规律的以及一次性的工作,启用编程并不是一个好的选择。因为少量的工作可以径直做完,并不需要通过编程处理(技术化解决的优势并不明显);而不规律的工作场景,由于所需要的个性化细节过多,一般的编程技术难以实现或者技术尚不能实现;对于一次性的工作需要考虑编程花费的时间成本与收获之间的平衡,花费过多而收获却少反倒不如直接处理来的快,这是在实用性能之外需要考虑的成本因素。
当然,有一部分朋友需要用编程解决一些专业性的计算和模拟测试等问题,这不在本文的讨论之列,这类需求应当归入专业需求者的行列,不适用于业余需求者。Python很容易?片面地强调Python学习起来很容易是不对的,事实上,容易与困难始终是相对的。我们可以说Python语言相对于其他语言来讲容易的多,因为有良好的扩展性能,许多应用场景可以由封装完备的库(可以理解为系统安装不同的操作软件,只不过这是非视窗化的软件)来实现,因此Python的程序代码比其他语言要简洁许多。早期最流行的一份学习资料是《简明Python教程》的小册子(原作Swaroop,C.H由沈洁元翻译,适用于python2.0版本),整个文档仅有70多页。支持3.0的版本也不需要购买教程,官方已经提供了最权威的版本(详见),这部分内容用一天的时间学习已经足够,哪怕没有编程经验的新手要学完整个课程也不会超过3天时间,这是该语言的特点,也是该语言“很容易”的重要依据。但是仅凭学完这些,你仍不能解决实际中的任何应用问题,因为需要继续了解和掌握的辅助知识还有很多,主要有两大方面:一方面你需要补充学习支持你需求的各类库,比如支持文本处理的Xlings、PyPDF2、python-docx等库,支持数据分析功能的Numpy、Pandas、Matplatlib等库,每一个库所能支持的功能不同,对操作系统的依赖也不相同,这时候我们往往需要阅读这些库的技术文档并做好试错的心理准备。
另一方面,你还需要补充学习一些其他计算机语言的相关知识,比如掌握基本的Linux或CMD操作命令,对于文字处理需求较多的朋友尤其需要掌握正则表达式(类似于Office中的通配符)的使用方法,对爬虫有需求的朋友仍需要学习网络开发语言或者相关技术(包括HTML、CSS、PHP、JS、JQ、AJAX中的一种或者多种组合)。几乎每个应用场景都需要你了解与这项功能相关联的知识,如果前期你对计算机的熟悉程度比较低的话,这里的知识缺失仍然会使你寸步难行。除此以外,你需要付出的额外成本还包括修正各种出其不意的报错,包括:(1)兼容性错误。比如一些库支持Mac系统并不支持Win系统,你需要在系统与库之间做出新的取舍,再比如系统升级而库未升级导致的新问题,软件冲突问题等。(2)程序本身的错误。有些错误是语言书写不规范导致的,这在学习初期尤其严重,比如以往用于2.0的函数或语法被用在了3.0的程序中,比如常见的数据类型、标点、空格以及由于多字、少字、错字引发的各类程序报错,以及对函数调用过程中的出现的规则错误,等等使你妨不胜妨。(3)网络错误或网络受限。在需要网络的各类编程活动中,通常会遇到网络的问题,比如受网络限制官网指定的Brew的安装方法在很多地区并不能操作成功,需要将官方源调整为民间备用源等。
以上是编程会遇到的普遍问题,那么接下来的技术局限是因人而异的另一重困难。几乎每个人都会经历这一过程,那种苦苦思索仍不得其解的感受,可能会使你兴奋,也可能会使你崩溃,你需要有这方面的心理准备。比如冒泡排序法作为经典的排序算法,几乎是每本程序书中都讲过的内容,有的人总也想不明白,也有些人知道了其中的原理,但就是写不出能够运行的程式。我最近曾尝试编写一个查询汉字五笔编码的工具时,尝试了许多方法也找寻了许多教程,但是仍然没有办法得到指定网页反馈来的结果。对于这种经历,解决的途径无非是两种:一是放弃;二是继续想办法攻克(多数人会选择前一项,这与我们处理工作问题时的遭遇相类似)。前者的一次放弃很可能只是下一次放弃的开端,直到你放弃整个语言。而后者意味着你可能需要投入更多的时间,这种投入可能会成功也可能未必会有收获。
每一个解决问题的人,总会遇到更多的问题,这是解题人的宿命。总的来说,Python在工作中的应用场景毋庸置疑,它几乎包含了所有的需要用到电脑工作的行业,而且未来的应用也很可观。当然,我们并不需要过分地追赶这门语言,毕竟要真正掌握这门语言其实并不算太简单,考虑到我们的应用场景和应用频率,对于已经有主要职业的从业者们来讲,学习过程中的所有困难都将折算成为时间成本,而你付出的成本越大这项技能的性价比就越低,这是每个尝试业余了解这项技能的人需要认真考虑的问题。不过,如果你把它当作业余爱好,那就另当别论了,毕竟编程也是一件和打王者荣耀一样很好玩的事。尤其,提出问题、分析问题、解决问题会伴随着整个编程过程,而这个过程会让我们习惯于拆解各项需求,思考实现的步骤,并为实现需求寻找解决方案,最后达到一步步实现需求的效果,这本身就是一种很好的思维训练。以上是本期Til与您分享的话题。感谢您的关注和支持。
查看全部
js提取指定网站内容 关于Python的劝学指南
关于Python的劝学指南
最近和几位朋友聊天,无意中谈到了关于Python的话题,这几位朋友之中有做财务的、有做投资的、也有项目负责人还有一些法务的朋友,他们所从事的行业与计算机没有太多关联,但是对Python所表现出来的热情,着实让我感到意外。他们选择学习Python的原因大致有两点:第一,认为Python很有用(有需求);第二,认为Python很简单(成本低,主要指时间成本)。那么,作为非计算机行业的从业人员,比如法律或者财税等其他非计算机行业的职业者,是否有学习这项技能的必要?这是我们今天要分享的话题。有言在先,分享这个话题,并不代表我本人的计算机水平达到了某种程度,只是作为一个业余学习Python的“过来人”,尝试对同样准备学习他的朋友们提供一些参考。对于专业选手来讲,本文的一些观点或许并不适用。
Python很有用?编程之路大致有两种途径,一种是先系统地学习编程语言,然后用语言解决遇到的问题;另一种是先有了应用场景需求,然后拆分问题寻找编程的实现方法。第一种属于被动接受的学习方式,也是传统教育普遍采用的培养方式,优点是基础往往比较牢靠,对知识也有全面的认知,缺点是学习的时间比较久,前期看不到成效并且不容易坚持下来。另一种属于主动探索的学习方式,通常是脱离学校教育后的主要学习方式,优点是目标和需求比较明确,能切实地解决问题,缺点也比较明显,没有系统化的指导和培养,往往需要自己寻找解决各种问题的方案,会走一些弯路,需要一点百折不挠的劲头才行。前者,适用于几乎所有的语言学习,后者适用于简单的语言学习,比如Python,也适用于从一门语言向另一门语言转变的学习,比如从C++转学Python。我是接触计算机比较早的一批人,起先学习的编程语言是C++,但是在取得该语言等级认证后的很长一段时间里,我并没有做出过任何一个实用的程序项目,因为如果不从事计算机行业,这些编程语言几乎没有任何应用场景,但Python算是个例外。我使用Python的第一个应用场景是毕业后的租房。当年的豆瓣网还是个很纯粹的平台,上海许多优质的房屋直租信息常常会在这个平台发布,对于有租房需求又没时间泡在平台上的我来讲,通过程序检索指定区域内的租房信息并将信息实时反馈给我,无疑是一个不错的方案,于是我向计算机专业的朋友请教了解决方案,他们向我推荐了Python(相比之下,这个场景用包括C语言在内的其他语言都很难实现),在见识到它强大的网络爬虫功能后,我开启了自己的Python之路。
律师的职业中,Python的应用场景主要围绕着文本文档处理和信息筛查展开,具体可以分为这样几类:(1)文本信息的筛查,尤其是对批量文本进行深度检索。比如,某年在某省高院协调处理地产纠纷诉讼事务的时候,我们对于解除房地产合作协议的诉讼案件收费标准与当地法院有了不同的看法,当地法院倾向于按照合同总价款的比例收取,而我们倾向于按照申请金额的比例收取,两个收费的标准相差了近600多万。为了佐证我们的观点,我们调取了北京、上海、广州等几个重要城市的近500余份裁判文书,并从中提取出诉讼费的裁判数据,形成了一份关于诉讼费收取的专项报告,其中对文书筛选的工作便是通过编程来完成的。(2)网站关键信息检索。比如,在一些官方网站检索功能受限的情况下,通过遍历网页的方式可以较准确地检索到官方公布的数据和文书(此处,忍住吐嘈某度在信息检索方面的商业化运作,以及某些行政类网站信息维护的质量,暂略去1000字)。又比如,在公报案例库还不健全的时候,通过自定义编程建立起自己的数据中心。(3)资料的收集、归纳和整理。比如,某年在研究某集团公司历年来的1100余件重大诉讼案件的过程中,提取历年来的案件信息,汇总整理报告的部分,也是通过编程辅助完成的。
又比如,在企业并购的业务中,需要对某上市公司历年公报信息进行收集,正常情况下历年公布的信息需要逐一下载,这项工作同样也可以通过程序自动处理。(4)格式化文本及文档。包括PDF及OFFICE格式文档操作,格式转换,信息提取与变更等,通常可以很方便地实现多任务的串联操作。对于想要接触Python的朋友,建议大家认真地了解一下编程与自身应用场景需求之间的匹配问题,如果有好的软件可以方便地实现这些应用场景,其实并不需要自行编程处理。比如,一些课程机构宣扬的用Python操作Excel,就我个人的体验来看,其中很多应用场景并不如使用Office办公软件方便,倒是财会的朋友常用的VBA功能可以尝试使用Python作为替换。技术发展至今,编程所能够解决的问题仍然局限于批量的、规律的以及重复性的一些工作,或者把一些分散的独立任务串联到一起,这是编程所能实现的基本任务。如果想要应对那些少量的、不规律的以及一次性的工作,启用编程并不是一个好的选择。因为少量的工作可以径直做完,并不需要通过编程处理(技术化解决的优势并不明显);而不规律的工作场景,由于所需要的个性化细节过多,一般的编程技术难以实现或者技术尚不能实现;对于一次性的工作需要考虑编程花费的时间成本与收获之间的平衡,花费过多而收获却少反倒不如直接处理来的快,这是在实用性能之外需要考虑的成本因素。
当然,有一部分朋友需要用编程解决一些专业性的计算和模拟测试等问题,这不在本文的讨论之列,这类需求应当归入专业需求者的行列,不适用于业余需求者。Python很容易?片面地强调Python学习起来很容易是不对的,事实上,容易与困难始终是相对的。我们可以说Python语言相对于其他语言来讲容易的多,因为有良好的扩展性能,许多应用场景可以由封装完备的库(可以理解为系统安装不同的操作软件,只不过这是非视窗化的软件)来实现,因此Python的程序代码比其他语言要简洁许多。早期最流行的一份学习资料是《简明Python教程》的小册子(原作Swaroop,C.H由沈洁元翻译,适用于python2.0版本),整个文档仅有70多页。支持3.0的版本也不需要购买教程,官方已经提供了最权威的版本(详见),这部分内容用一天的时间学习已经足够,哪怕没有编程经验的新手要学完整个课程也不会超过3天时间,这是该语言的特点,也是该语言“很容易”的重要依据。但是仅凭学完这些,你仍不能解决实际中的任何应用问题,因为需要继续了解和掌握的辅助知识还有很多,主要有两大方面:一方面你需要补充学习支持你需求的各类库,比如支持文本处理的Xlings、PyPDF2、python-docx等库,支持数据分析功能的Numpy、Pandas、Matplatlib等库,每一个库所能支持的功能不同,对操作系统的依赖也不相同,这时候我们往往需要阅读这些库的技术文档并做好试错的心理准备。
另一方面,你还需要补充学习一些其他计算机语言的相关知识,比如掌握基本的Linux或CMD操作命令,对于文字处理需求较多的朋友尤其需要掌握正则表达式(类似于Office中的通配符)的使用方法,对爬虫有需求的朋友仍需要学习网络开发语言或者相关技术(包括HTML、CSS、PHP、JS、JQ、AJAX中的一种或者多种组合)。几乎每个应用场景都需要你了解与这项功能相关联的知识,如果前期你对计算机的熟悉程度比较低的话,这里的知识缺失仍然会使你寸步难行。除此以外,你需要付出的额外成本还包括修正各种出其不意的报错,包括:(1)兼容性错误。比如一些库支持Mac系统并不支持Win系统,你需要在系统与库之间做出新的取舍,再比如系统升级而库未升级导致的新问题,软件冲突问题等。(2)程序本身的错误。有些错误是语言书写不规范导致的,这在学习初期尤其严重,比如以往用于2.0的函数或语法被用在了3.0的程序中,比如常见的数据类型、标点、空格以及由于多字、少字、错字引发的各类程序报错,以及对函数调用过程中的出现的规则错误,等等使你妨不胜妨。(3)网络错误或网络受限。在需要网络的各类编程活动中,通常会遇到网络的问题,比如受网络限制官网指定的Brew的安装方法在很多地区并不能操作成功,需要将官方源调整为民间备用源等。
以上是编程会遇到的普遍问题,那么接下来的技术局限是因人而异的另一重困难。几乎每个人都会经历这一过程,那种苦苦思索仍不得其解的感受,可能会使你兴奋,也可能会使你崩溃,你需要有这方面的心理准备。比如冒泡排序法作为经典的排序算法,几乎是每本程序书中都讲过的内容,有的人总也想不明白,也有些人知道了其中的原理,但就是写不出能够运行的程式。我最近曾尝试编写一个查询汉字五笔编码的工具时,尝试了许多方法也找寻了许多教程,但是仍然没有办法得到指定网页反馈来的结果。对于这种经历,解决的途径无非是两种:一是放弃;二是继续想办法攻克(多数人会选择前一项,这与我们处理工作问题时的遭遇相类似)。前者的一次放弃很可能只是下一次放弃的开端,直到你放弃整个语言。而后者意味着你可能需要投入更多的时间,这种投入可能会成功也可能未必会有收获。
每一个解决问题的人,总会遇到更多的问题,这是解题人的宿命。总的来说,Python在工作中的应用场景毋庸置疑,它几乎包含了所有的需要用到电脑工作的行业,而且未来的应用也很可观。当然,我们并不需要过分地追赶这门语言,毕竟要真正掌握这门语言其实并不算太简单,考虑到我们的应用场景和应用频率,对于已经有主要职业的从业者们来讲,学习过程中的所有困难都将折算成为时间成本,而你付出的成本越大这项技能的性价比就越低,这是每个尝试业余了解这项技能的人需要认真考虑的问题。不过,如果你把它当作业余爱好,那就另当别论了,毕竟编程也是一件和打王者荣耀一样很好玩的事。尤其,提出问题、分析问题、解决问题会伴随着整个编程过程,而这个过程会让我们习惯于拆解各项需求,思考实现的步骤,并为实现需求寻找解决方案,最后达到一步步实现需求的效果,这本身就是一种很好的思维训练。以上是本期Til与您分享的话题。感谢您的关注和支持。
js提取指定网站内容提取方法(form转换提取内容)
网站优化 • 优采云 发表了文章 • 0 个评论 • 75 次浏览 • 2022-04-30 22:01
js提取指定网站内容提取方法也较多,一般针对各网站提取来源网站,有full-text提取方法、有response转换提取方法、还有用webextension提取等,也就是我们平常用的jsxml提取。1.full-text方法,支持jscss等内容提取2.response转换提取,支持jscss等内容提取3.webextension提取,可提取jscss等内容提取方法前两种,建议都可以直接在浏览器提取的话,会很麻烦。建议用用网上的webextension提取方法,或者是通过浏览器的网页调试来提取。
一、full-text提取
1、一条提取10条,这种方法适合前端,在接下来的10条中,同一段内容出现的内容较多,时间不长的情况下。
2、从10条提取到10条内容,这种方法适合前端基础较差,希望速度快,批量提取js,内容较少的情况下。
3、n条提取前10条,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
4、如果条件允许,直接从全网提取,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
二、response转换提取方法(form提取方法)
1、form提取内容,这种方法适合前端基础一般,批量提取css,js等,
2、response提取内容,这种方法适合前端基础一般,批量提取css,js等,
3、批量转换的另一种情况是将请求中的async模块转换成string类型的对象。
4、如果请求中有multipart的参数转换为text类型,适合批量提取js,css等。 查看全部
js提取指定网站内容提取方法(form转换提取内容)
js提取指定网站内容提取方法也较多,一般针对各网站提取来源网站,有full-text提取方法、有response转换提取方法、还有用webextension提取等,也就是我们平常用的jsxml提取。1.full-text方法,支持jscss等内容提取2.response转换提取,支持jscss等内容提取3.webextension提取,可提取jscss等内容提取方法前两种,建议都可以直接在浏览器提取的话,会很麻烦。建议用用网上的webextension提取方法,或者是通过浏览器的网页调试来提取。
一、full-text提取
1、一条提取10条,这种方法适合前端,在接下来的10条中,同一段内容出现的内容较多,时间不长的情况下。
2、从10条提取到10条内容,这种方法适合前端基础较差,希望速度快,批量提取js,内容较少的情况下。
3、n条提取前10条,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
4、如果条件允许,直接从全网提取,这种方法适合前端基础较好,批量提取js,内容较多的情况下。
二、response转换提取方法(form提取方法)
1、form提取内容,这种方法适合前端基础一般,批量提取css,js等,
2、response提取内容,这种方法适合前端基础一般,批量提取css,js等,
3、批量转换的另一种情况是将请求中的async模块转换成string类型的对象。
4、如果请求中有multipart的参数转换为text类型,适合批量提取js,css等。
实用脚本!Python 提取 PDF 指定内容生成新文件!
网站优化 • 优采云 发表了文章 • 0 个评论 • 354 次浏览 • 2022-04-30 00:15
很多时候,我们并不会预知希望提取的页号,而是希望将包含指定内容的页面提取合并为新PDF,本文就以两个真实需求为例进行讲解。
01
需求描述
数据是一份有286页的上市公司公开年报PDF,大致如下
现在需要利用 Python 完成以下两个需求
“
需求一:提取所有包含 战略 二字的页面并合并新PDF
需求二:提取所有包含图片的页面,并分别保存为 PDF 文件
”
02
前置知识和逻辑梳理
2.1 PyPDF2 模块实现合并
PyPDF2 导入模块的代码常常是:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />
这里导入了两个方法:
PdfFileReader 可以理解为读取器PdfFileWriter 可以理解为写入器
利用 PyPDF2 实现合并运用的一下逻辑:
读取器将所有pdf读取一遍读取器将读取的内容交给写入器写入器统一输出到一个新pdf
隐含知识点:读取器只能将读取的内容一页一页交给写入器
2.2 获取与添加页面
之前我们的推文中提到这两个代码,下面列出作为复习:
.getPage 获取特定页.addPage 添加特定页2.3 图片和文字的处理
要实现本文的需求还要做到很重要的一个判断:确定页面中有无包含的文字或图片
判断是否包含特定的文字比较简单,遍历每一页的时候都将包含的文本抽提出,做字符串层面的判断即可,代码思路:
利用 pdfplumber 打开PDF 文件获取指定的页,或者遍历每一页利用 .extract_text() 方法提取当前页的文字判断 “战略” 是否在提取的文字中
判断是否包含图片,思路和上面是类似的,但方法不同。图片考虑用正则的方法识别,用 fitz 和 re 配合,具体见下文代码
03
代码实现
3.1 需求一的实现
首先来完成需求一的任务,导入需要用到的库:读取写入PDF文件的 PyPDF2 以及抽提文本的 pdfplumber
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br />
指定文件所在的路径,同时初始化写入器,将文件交给读取器:
path = r'C:\xxxxxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />
以上下文管理器形式通过 pdfplumber 打开文件,同时用 .getNumPages 获取读取器的最大页利于遍历每一页来抽提文字:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br />
我们抽提文字的目的是用来判断,将符合要求的页码作为读取器 .getPage 的参数,最后用 .addPage 交给写入器:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br />
完成识别后让写入器输出为需要的文件名:
with open(path + r'\new_公司年报.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
至此,我们就完成了包含特定文字内容页面的提取,并整合成一个PDF。所有的页面均包含“战略”二字:
需求一完整代码如下,感兴趣的读者可以自行研究
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br /><br />path = r'C:\xxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br /><br />with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br /><br />with open(path + r'\new_公司年报1.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
3.2 需求二的实现
接下来完成需求二的任务。首先导入需要的库:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br />
指定文件所在的路径:
path = r'C:\xxxxxx'<br />
正则识别图片的部分不细讲,之前的推文已经介绍过,我们直接看代码:
page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br /><br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br />
获取到所有包含图片的页面后,再结合读取器和写入器的配合就能完成新 PDF 的产生。注意本需求是所有图片单独输出,因此获取到页面后交给写入器直接输出成文件:
pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
至此也完成了第二个需求。需要说明的是目前没有非常完美提取PDF图片的方法,本案例介绍的方法识别图片也并不稳定。读者可以利用自己的数据多做尝试。完整代码如下:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br /><br />path = r'C:\xxx'<br /><br />page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br /><br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
实现这两个单个需求后,就可以将相关代码封装并结合os等模块实现批量操作,解放双手。
近期阅读学习推荐:
如何找到我:
<p data-mid="" style="outline: 0px;max-width: 100%;font-size: 14px;font-family: PingFangSC-Medium, "PingFang SC";font-weight: bold;color: rgb(183, 78, 67);line-height: 20px;box-sizing: border-box !important;overflow-wrap: break-word !important;">分享
收藏
点赞
在看</p> 查看全部
实用脚本!Python 提取 PDF 指定内容生成新文件!
很多时候,我们并不会预知希望提取的页号,而是希望将包含指定内容的页面提取合并为新PDF,本文就以两个真实需求为例进行讲解。
01
需求描述
数据是一份有286页的上市公司公开年报PDF,大致如下
现在需要利用 Python 完成以下两个需求
“
需求一:提取所有包含 战略 二字的页面并合并新PDF
需求二:提取所有包含图片的页面,并分别保存为 PDF 文件
”
02
前置知识和逻辑梳理
2.1 PyPDF2 模块实现合并
PyPDF2 导入模块的代码常常是:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />
这里导入了两个方法:
PdfFileReader 可以理解为读取器PdfFileWriter 可以理解为写入器
利用 PyPDF2 实现合并运用的一下逻辑:
读取器将所有pdf读取一遍读取器将读取的内容交给写入器写入器统一输出到一个新pdf
隐含知识点:读取器只能将读取的内容一页一页交给写入器
2.2 获取与添加页面
之前我们的推文中提到这两个代码,下面列出作为复习:
.getPage 获取特定页.addPage 添加特定页2.3 图片和文字的处理
要实现本文的需求还要做到很重要的一个判断:确定页面中有无包含的文字或图片
判断是否包含特定的文字比较简单,遍历每一页的时候都将包含的文本抽提出,做字符串层面的判断即可,代码思路:
利用 pdfplumber 打开PDF 文件获取指定的页,或者遍历每一页利用 .extract_text() 方法提取当前页的文字判断 “战略” 是否在提取的文字中
判断是否包含图片,思路和上面是类似的,但方法不同。图片考虑用正则的方法识别,用 fitz 和 re 配合,具体见下文代码
03
代码实现
3.1 需求一的实现
首先来完成需求一的任务,导入需要用到的库:读取写入PDF文件的 PyPDF2 以及抽提文本的 pdfplumber
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br />
指定文件所在的路径,同时初始化写入器,将文件交给读取器:
path = r'C:\xxxxxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />
以上下文管理器形式通过 pdfplumber 打开文件,同时用 .getNumPages 获取读取器的最大页利于遍历每一页来抽提文字:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br />
我们抽提文字的目的是用来判断,将符合要求的页码作为读取器 .getPage 的参数,最后用 .addPage 交给写入器:
with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br />
完成识别后让写入器输出为需要的文件名:
with open(path + r'\new_公司年报.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
至此,我们就完成了包含特定文字内容页面的提取,并整合成一个PDF。所有的页面均包含“战略”二字:
需求一完整代码如下,感兴趣的读者可以自行研究
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import pdfplumber<br /><br />path = r'C:\xxx'<br />pdf_writer = PdfFileWriter()<br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br /><br />with pdfplumber.open(path + r'\公司年报.PDF') as pdf:<br /> for i in range(pdf_reader.getNumPages()):<br /> page = pdf.pages[i]<br /> print(page.extract_text())<br /> if '战略' in page.extract_text():<br /> pdf_writer.addPage(pdf_reader.getPage(i))<br /> print(i + 1, page.extract_text())<br /><br />with open(path + r'\new_公司年报1.pdf', 'wb') as out:<br /> pdf_writer.write(out)<br />
3.2 需求二的实现
接下来完成需求二的任务。首先导入需要的库:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br />
指定文件所在的路径:
path = r'C:\xxxxxx'<br />
正则识别图片的部分不细讲,之前的推文已经介绍过,我们直接看代码:
page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br /><br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br />
获取到所有包含图片的页面后,再结合读取器和写入器的配合就能完成新 PDF 的产生。注意本需求是所有图片单独输出,因此获取到页面后交给写入器直接输出成文件:
pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
至此也完成了第二个需求。需要说明的是目前没有非常完美提取PDF图片的方法,本案例介绍的方法识别图片也并不稳定。读者可以利用自己的数据多做尝试。完整代码如下:
from PyPDF2 import PdfFileReader, PdfFileWriter<br />import fitz<br />import re<br />import os<br /><br />path = r'C:\xxx'<br /><br />page_lst = []<br />checkImg = r"/Subtype(?= */Image)"<br />pdf = fitz.open(path + r'\公司年报.PDF')<br />lenXREF = pdf._getXrefLength()<br />for i in range(lenXREF):<br /> text = pdf._getXrefString(i)<br /> isImage = re.search(checkImg, text)<br /> if isImage:<br /> page_lst.append(i)<br /><br />print(page_lst)<br /><br />pdf_reader = PdfFileReader(path + r'\公司年报.PDF')<br />for page in page_lst:<br /> pdf_writer = PdfFileWriter()<br /> pdf_writer.addPage(pdf_reader.getPage(page))<br /> with open(path + r'\公司年报_{}.pdf'.format(page + 1), 'wb') as out:<br /> pdf_writer.write(out)<br />
实现这两个单个需求后,就可以将相关代码封装并结合os等模块实现批量操作,解放双手。
近期阅读学习推荐:
如何找到我:
收藏
点赞
在看</p>
js提取指定网站内容(js提取指定网站内容依赖进行处理方法介绍)
网站优化 • 优采云 发表了文章 • 0 个评论 • 48 次浏览 • 2022-04-19 19:01
js提取指定网站内容:采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。
js提取指定网站内容采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。④将指定的html格式存储到html文件中。
js提取指定网站内容,采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容,采用json读取,
自己处理自己网站内容,除了通过json格式存储外,自己还可以写个业务逻辑,判断自己网站的内容是否有误,如果有误就调用系统生成的文档进行校验。 查看全部
js提取指定网站内容(js提取指定网站内容依赖进行处理方法介绍)
js提取指定网站内容:采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。
js提取指定网站内容采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容:①建立一个templatereview.js,用来提取指定网站内容;②开始写templatereview.js的业务逻辑,引入localjs后。③将提取的内容传入到models文件,赋值给models.js。④将指定的html格式存储到html文件中。
js提取指定网站内容,采用localjs,即gulp模块(gulp通过`local`在conf目录下注册localjs依赖进行处理)自己处理网站内容,采用json读取,
自己处理自己网站内容,除了通过json格式存储外,自己还可以写个业务逻辑,判断自己网站的内容是否有误,如果有误就调用系统生成的文档进行校验。
js提取指定网站内容(优质文章,第一时间送达!(掘金GNE)|掘金)
网站优化 • 优采云 发表了文章 • 0 个评论 • 65 次浏览 • 2022-04-18 16:39
高品质文章,尽快发货!
正文 | 青南
出处 | 掘金
GNE(GeneralNewsExtractor)是一个综合新闻网站文本提取模块,输入一个新闻网页的HTML,输出文本中的文本内容、标题、作者、发布时间、图片地址和源代码文本所在的标签。GNE在提取今日头条、网易新闻、游民新闻、观察者网、凤凰网、腾讯新闻、ReadHub、新浪新闻等数百条中文新闻网站方面非常有效,准确率几乎可以达到100% . .
使用非常简单:
from gne import GeneralNewsExtractor
extractor = GeneralNewsExtractor
html = '网站源代码'
result = extractor.extract(html)
print(result)
GNE的输入是js渲染后的HTML代码,所以GNE可以配合Selenium或者Pyppeteer使用。
下图是GNE用Selenium实现的Demo:
对应的代码是:
import time
from gne import GeneralNewsExtractor
from selenium.webdriver import Chrome
driver = Chrome('./chromedriver')
driver.get('https://www.toutiao.com/a6766986211736158727/')
time.sleep(3)
extractor = GeneralNewsExtractor
result = extractor.extract(driver.page_source)
print(result)
下图是GNE用Pyppeteer实现的Demo:
对应的代码如下:
import asyncio
from gne import GeneralNewsExtractor
from pyppeteer import launch
async def main:
browser = await launch(executablePath='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome')
page = await browser.newPage
await page.goto('https://news.163.com/20/0101/17/F1QS286R000187R2.html')
extractor = GeneralNewsExtractor
result = extractor.extract(await page.content)
print(result)
input('检查完成以后回到这里按下任意键')
asyncio.run(main)
如何安装 GNE
现在您可以使用 pip 直接安装 GNE:
pip install gne
如果访问官方pypi源太慢,也可以使用网易源:
pip install gne -i https://mirrors.163.com/pypi/simple/
安装过程如下图所示:
功能获取文本源代码
当 extract 方法只传入网页的源代码而不添加任何额外的参数时,GNE 返回以下字段:
有的朋友可能想获取新闻正文所在标签的源码。这时候可以将with_body_html参数传给extract方法,设置为True:
extractor = GeneralNewsExtractor
extractor.extract(html, with_body_html=True)
返回的数据中会添加一个字段body_html,其值为body对应的HTML源代码。
运行效果如下图所示:
总是返回图像的绝对路径
默认情况下,如果新闻中的图片使用相对路径,则GNE返回的images字段对应的值也是图片的相对路径列表。
如果想让 GNE 总是返回绝对路径,那么可以在 extract 方法中添加 host 参数,这个参数的值就是图片的域名,例如:
extractor = GeneralNewsExtractor
extractor.extract(html, host='https://www.kingname.info')
这样,如果新闻中的图片是/images/pic.png,GNE会自动将其更改为。
指定新闻标题所在的 XPath
GNE 预定义了一组用于提取新闻标题的 XPath 和正则表达式。但是,一些特殊新闻网站可能无法提取标题。在这种情况下,您可以在 extract 方法中指定 title_xpath 参数来提取新闻标题:
extractor = GeneralNewsExtractor
extractor.extract(html, title_xpath='//title/text')
尽早去除嘈杂的标签
一些新闻下可能会有长篇大论的评论。这些评论看起来比新闻文本“更像”。为了防止它们干扰新闻提取,可以将noise_node_list参数传递给extract方法,提前去除这些噪声节点。noise_node_list 的值是一个或多个 XPath 的列表:
extractor = GeneralNewsExtractor
extractor.extract(html, noise_node_list=['//div[@class="comment-list"]', '//*[@style="display:none"]'])
使用配置文件
API中的参数title_xpath、host、noise_node_list、with_body_html除了直接写入extract方法外,还可以通过配置文件进行设置。
请在项目的根目录中创建一个文件 .gne。配置文件可以是 YAML 格式,也可以是 JSON 格式。
title:
xpath: //title/text
host: https://www.xxx.com
noise_node_list:
- //div[@class="comment-list"]
- //*[@style="display:none"]
with_body_html: true
{
"title": {
"xpath": "//title/text"
},
"host": "https://www.xxx.com",
"noise_node_list": ["//div[@class="comment-list"]",
"//*[@style="display:none"]"],
"with_body_html": true
}
这两个符号是完全等价的。
配置文件和extract方法的参数一样,并不是所有的字段都需要提供。您可以组合并填写您需要的字段。
如果 extract 方法和 .gne 配置文件中的参数具有不同的值,则 extract 方法中的参数具有更高的优先级。
FAQGeneralNewsExtractor(以下简称GNE)是爬虫吗?
GNE 不是爬虫,它的项目名称 General News Extractor 代表 General News Extractor。它的输入是HTML,它的输出是一个收录新闻标题、新闻正文、作者、出版时间的字典。您需要找到自己的方式来获取目标页面的 HTML。
GNE 不会也不会在未来提供请求网页的功能。
GNE 支持翻页吗?
GNE 不支持翻页。因为GNE不提供网页请求的功能,所以需要自己获取每个页面的HTML,单独传给GNE。
GNE 支持哪些版本的 Python?
不少于 Python 3.6.0
我把requests/Scrapy得到的HTML传给GNE,为什么不能提取body?
GNE 基于 HTML 提取文本,因此传入的 HTML 必须是 JavaScript 渲染的 HTML。但是,requests 和 Scrapy 只在 JavaScript 渲染之前获取源代码,因此无法正确提取。
此外,还有一些网页,比如今日头条,其新闻文本实际上是直接用JSON格式的网页源代码编写的。当页面在浏览器上打开时,JavaScript 将源代码中的文本解析为 HTML。在这种情况下,您不会在 Chrome 上看到 Ajax 请求。
所以建议大家使用 Puppeteer/Pyppeteer/Selenium 等工具来获取渲染后的 HTML 并传入 GNE。
原文链接: 查看全部
js提取指定网站内容(优质文章,第一时间送达!(掘金GNE)|掘金)
高品质文章,尽快发货!
正文 | 青南
出处 | 掘金
GNE(GeneralNewsExtractor)是一个综合新闻网站文本提取模块,输入一个新闻网页的HTML,输出文本中的文本内容、标题、作者、发布时间、图片地址和源代码文本所在的标签。GNE在提取今日头条、网易新闻、游民新闻、观察者网、凤凰网、腾讯新闻、ReadHub、新浪新闻等数百条中文新闻网站方面非常有效,准确率几乎可以达到100% . .
使用非常简单:
from gne import GeneralNewsExtractor
extractor = GeneralNewsExtractor
html = '网站源代码'
result = extractor.extract(html)
print(result)
GNE的输入是js渲染后的HTML代码,所以GNE可以配合Selenium或者Pyppeteer使用。
下图是GNE用Selenium实现的Demo:
对应的代码是:
import time
from gne import GeneralNewsExtractor
from selenium.webdriver import Chrome
driver = Chrome('./chromedriver')
driver.get('https://www.toutiao.com/a6766986211736158727/')
time.sleep(3)
extractor = GeneralNewsExtractor
result = extractor.extract(driver.page_source)
print(result)
下图是GNE用Pyppeteer实现的Demo:
对应的代码如下:
import asyncio
from gne import GeneralNewsExtractor
from pyppeteer import launch
async def main:
browser = await launch(executablePath='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome')
page = await browser.newPage
await page.goto('https://news.163.com/20/0101/17/F1QS286R000187R2.html')
extractor = GeneralNewsExtractor
result = extractor.extract(await page.content)
print(result)
input('检查完成以后回到这里按下任意键')
asyncio.run(main)
如何安装 GNE
现在您可以使用 pip 直接安装 GNE:
pip install gne
如果访问官方pypi源太慢,也可以使用网易源:
pip install gne -i https://mirrors.163.com/pypi/simple/
安装过程如下图所示:
功能获取文本源代码
当 extract 方法只传入网页的源代码而不添加任何额外的参数时,GNE 返回以下字段:
有的朋友可能想获取新闻正文所在标签的源码。这时候可以将with_body_html参数传给extract方法,设置为True:
extractor = GeneralNewsExtractor
extractor.extract(html, with_body_html=True)
返回的数据中会添加一个字段body_html,其值为body对应的HTML源代码。
运行效果如下图所示:
总是返回图像的绝对路径
默认情况下,如果新闻中的图片使用相对路径,则GNE返回的images字段对应的值也是图片的相对路径列表。
如果想让 GNE 总是返回绝对路径,那么可以在 extract 方法中添加 host 参数,这个参数的值就是图片的域名,例如:
extractor = GeneralNewsExtractor
extractor.extract(html, host='https://www.kingname.info')
这样,如果新闻中的图片是/images/pic.png,GNE会自动将其更改为。
指定新闻标题所在的 XPath
GNE 预定义了一组用于提取新闻标题的 XPath 和正则表达式。但是,一些特殊新闻网站可能无法提取标题。在这种情况下,您可以在 extract 方法中指定 title_xpath 参数来提取新闻标题:
extractor = GeneralNewsExtractor
extractor.extract(html, title_xpath='//title/text')
尽早去除嘈杂的标签
一些新闻下可能会有长篇大论的评论。这些评论看起来比新闻文本“更像”。为了防止它们干扰新闻提取,可以将noise_node_list参数传递给extract方法,提前去除这些噪声节点。noise_node_list 的值是一个或多个 XPath 的列表:
extractor = GeneralNewsExtractor
extractor.extract(html, noise_node_list=['//div[@class="comment-list"]', '//*[@style="display:none"]'])
使用配置文件
API中的参数title_xpath、host、noise_node_list、with_body_html除了直接写入extract方法外,还可以通过配置文件进行设置。
请在项目的根目录中创建一个文件 .gne。配置文件可以是 YAML 格式,也可以是 JSON 格式。
title:
xpath: //title/text
host: https://www.xxx.com
noise_node_list:
- //div[@class="comment-list"]
- //*[@style="display:none"]
with_body_html: true
{
"title": {
"xpath": "//title/text"
},
"host": "https://www.xxx.com",
"noise_node_list": ["//div[@class="comment-list"]",
"//*[@style="display:none"]"],
"with_body_html": true
}
这两个符号是完全等价的。
配置文件和extract方法的参数一样,并不是所有的字段都需要提供。您可以组合并填写您需要的字段。
如果 extract 方法和 .gne 配置文件中的参数具有不同的值,则 extract 方法中的参数具有更高的优先级。
FAQGeneralNewsExtractor(以下简称GNE)是爬虫吗?
GNE 不是爬虫,它的项目名称 General News Extractor 代表 General News Extractor。它的输入是HTML,它的输出是一个收录新闻标题、新闻正文、作者、出版时间的字典。您需要找到自己的方式来获取目标页面的 HTML。
GNE 不会也不会在未来提供请求网页的功能。
GNE 支持翻页吗?
GNE 不支持翻页。因为GNE不提供网页请求的功能,所以需要自己获取每个页面的HTML,单独传给GNE。
GNE 支持哪些版本的 Python?
不少于 Python 3.6.0
我把requests/Scrapy得到的HTML传给GNE,为什么不能提取body?
GNE 基于 HTML 提取文本,因此传入的 HTML 必须是 JavaScript 渲染的 HTML。但是,requests 和 Scrapy 只在 JavaScript 渲染之前获取源代码,因此无法正确提取。
此外,还有一些网页,比如今日头条,其新闻文本实际上是直接用JSON格式的网页源代码编写的。当页面在浏览器上打开时,JavaScript 将源代码中的文本解析为 HTML。在这种情况下,您不会在 Chrome 上看到 Ajax 请求。
所以建议大家使用 Puppeteer/Pyppeteer/Selenium 等工具来获取渲染后的 HTML 并传入 GNE。
原文链接:
js提取指定网站内容(波波带你一步一步手动提取网页视频(图))
网站优化 • 优采云 发表了文章 • 0 个评论 • 49 次浏览 • 2022-04-18 00:02
在生活中,我们经常会看一些网络视频,其中一些感觉非常好,我们都希望自己下载下来采集,但是却常常尝试失败。
这次博博会带你一步步手动提取网络视频。
首先,需要一些准备知识。
一、网络视频
网页中的视频一般是通过视频标签来播放的,还会有一些其他的技术手段来避免或隐藏视频。避开视频可能会使用其他一些播放器来播放视频,隐藏视频一般会将视频放在一个iframe中,这个后续具体案例会介绍。
二、视频链接类型
网页中的大部分视频都会有 mp4 链接和 m3u8 链接。前者易于提取和下载,而后者则比较麻烦。
三、谷歌浏览器
谷歌浏览器是我们下一步的关键。当然,一些360浏览器、QQ浏览器等自带chrome内核的浏览器也是可以的。
在chrome浏览器中,我们需要熟悉控制台,可以通过F12调出,或者在菜单中打开JavaScript控制台。具体可以从百度打开到不同的浏览器,一般是windows的F12。
如下图所示,控制台看起来大致。后期我们会用到Elements、Network、Application,主要是Network,其次是Elements。可能会用到一些应用场景。左上角还有一个箭头按钮,可以用来定位视频标签。
基础的就先到这里,后面再做一些实际操作。
期待下一个文章。 查看全部
js提取指定网站内容(波波带你一步一步手动提取网页视频(图))
在生活中,我们经常会看一些网络视频,其中一些感觉非常好,我们都希望自己下载下来采集,但是却常常尝试失败。
这次博博会带你一步步手动提取网络视频。
首先,需要一些准备知识。
一、网络视频
网页中的视频一般是通过视频标签来播放的,还会有一些其他的技术手段来避免或隐藏视频。避开视频可能会使用其他一些播放器来播放视频,隐藏视频一般会将视频放在一个iframe中,这个后续具体案例会介绍。
二、视频链接类型
网页中的大部分视频都会有 mp4 链接和 m3u8 链接。前者易于提取和下载,而后者则比较麻烦。
三、谷歌浏览器
谷歌浏览器是我们下一步的关键。当然,一些360浏览器、QQ浏览器等自带chrome内核的浏览器也是可以的。
在chrome浏览器中,我们需要熟悉控制台,可以通过F12调出,或者在菜单中打开JavaScript控制台。具体可以从百度打开到不同的浏览器,一般是windows的F12。
如下图所示,控制台看起来大致。后期我们会用到Elements、Network、Application,主要是Network,其次是Elements。可能会用到一些应用场景。左上角还有一个箭头按钮,可以用来定位视频标签。
基础的就先到这里,后面再做一些实际操作。
期待下一个文章。
js提取指定网站内容(.js,request和cheerio在我学校的课程表网站上抓取链接)
网站优化 • 优采云 发表了文章 • 0 个评论 • 50 次浏览 • 2022-04-17 16:01
我正在尝试使用 Node.js、request 和 Cheerio 来抓取我学校课程表上的链接网站。但是,我的代码并未到达所有主题链接。
此处链接到课程表网站。
这是我的代码:
var express = require('express');
var request = require('request');
var cheerio = require('cheerio');
var app = express();
app.get('/subjects', function(req, res) {
var URL = 'http://courseschedules.njit.ed ... 3B%3B
request(URL, function(error, response, body) {
if(!error) {
var $ = cheerio.load(body);
$('.courseList_section a').each(function() {
var text = $(this).text();
var link = $(this).attr('href');
console.log(text + ' --> ' + link);
});
}
else {
console.log('There was an error!');
}
});
});
app.listen('8080');
console.log('Magic happens on port 8080!');
我的输出可以在这里找到。
从我的输出中可以看出,缺少一些链接。更具体地说,来自“A”、“I(续)”和 R“(续)”部分的链接。这些也是每列的第一部分。
每个部分都收录在自己的 div 中,类名为“courseList_section”,所以我不明白为什么“.courseList_section a”没有遍历所有链接。我错过了一些明显的东西吗?非常感谢任何和所有见解。
提前谢谢你! 查看全部
js提取指定网站内容(.js,request和cheerio在我学校的课程表网站上抓取链接)
我正在尝试使用 Node.js、request 和 Cheerio 来抓取我学校课程表上的链接网站。但是,我的代码并未到达所有主题链接。
此处链接到课程表网站。
这是我的代码:
var express = require('express');
var request = require('request');
var cheerio = require('cheerio');
var app = express();
app.get('/subjects', function(req, res) {
var URL = 'http://courseschedules.njit.ed ... 3B%3B
request(URL, function(error, response, body) {
if(!error) {
var $ = cheerio.load(body);
$('.courseList_section a').each(function() {
var text = $(this).text();
var link = $(this).attr('href');
console.log(text + ' --> ' + link);
});
}
else {
console.log('There was an error!');
}
});
});
app.listen('8080');
console.log('Magic happens on port 8080!');
我的输出可以在这里找到。
从我的输出中可以看出,缺少一些链接。更具体地说,来自“A”、“I(续)”和 R“(续)”部分的链接。这些也是每列的第一部分。
每个部分都收录在自己的 div 中,类名为“courseList_section”,所以我不明白为什么“.courseList_section a”没有遍历所有链接。我错过了一些明显的东西吗?非常感谢任何和所有见解。
提前谢谢你!
js提取指定网站内容(js提取指定网站内容,我自己用php封装了一个go-js-main.php,)
网站优化 • 优采云 发表了文章 • 0 个评论 • 87 次浏览 • 2022-04-17 03:01
js提取指定网站内容,我自己用php封装了一个go-js-main.php,用js调用go函数,生成go页面,然后js用php的ajax去调用,来展示指定的内容。go-js-main.php:go-js-main.phpsendto_some_msgphp提取指定站点内容,还是js封装了一个go.php,然后用php的ajax发送请求,来调用go生成的内容,就可以提取出来。go.php:go.phpsendto_some_msg。
可以在js中判断method而不是url比如xml转码erv。php/xml/source:php:6。2。0/usr/jasmine/commonjs/error。php请求页面的时候xml={"name":"rq@gmail。com","version":1。0,"province":"江苏省无锡市","shipper_pro":"echo","port":"33018","confirm_time":"2018-08-1314:10:42","placement":"phpstorm","request_url":"","response_url":""}。
可以看看golang写的web服务器,提供mysql的访问。(golang2.6:)可以看看golang的后端服务器生态。
java模块化开发,数据接口可按需服务。这些it产品背后是具有web架构体系的巨大体系。
很多啊java可以gorustpythonmicrosoft都有boost 查看全部
js提取指定网站内容(js提取指定网站内容,我自己用php封装了一个go-js-main.php,)
js提取指定网站内容,我自己用php封装了一个go-js-main.php,用js调用go函数,生成go页面,然后js用php的ajax去调用,来展示指定的内容。go-js-main.php:go-js-main.phpsendto_some_msgphp提取指定站点内容,还是js封装了一个go.php,然后用php的ajax发送请求,来调用go生成的内容,就可以提取出来。go.php:go.phpsendto_some_msg。
可以在js中判断method而不是url比如xml转码erv。php/xml/source:php:6。2。0/usr/jasmine/commonjs/error。php请求页面的时候xml={"name":"rq@gmail。com","version":1。0,"province":"江苏省无锡市","shipper_pro":"echo","port":"33018","confirm_time":"2018-08-1314:10:42","placement":"phpstorm","request_url":"","response_url":""}。
可以看看golang写的web服务器,提供mysql的访问。(golang2.6:)可以看看golang的后端服务器生态。
java模块化开发,数据接口可按需服务。这些it产品背后是具有web架构体系的巨大体系。
很多啊java可以gorustpythonmicrosoft都有boost
js提取指定网站内容(js提取指定网站内容是什么?怎么做好php提取)
网站优化 • 优采云 发表了文章 • 0 个评论 • 40 次浏览 • 2022-04-15 12:03
js提取指定网站内容或者是加入到任何一种后端语言的数据库中,比如百度抓取的是搜索引擎的数据,抓取的是数据库里的数据,微信抓取的是网页数据等等。js接收的只是一个字符串,js加在百度搜索页面上的,它把抓取到的网页数据提取出来存入后端语言数据库里,当然能加载进来。并没有什么语言之间的转换,再说这个速度慢到谁也无法接受的程度了。
所以,你抓取数据并不需要把原网页上所有的字符串都转换,不然谁写得出这么丑陋的js库。而是抓取要抓取的网页数据,并加上你需要的数据,然后就可以拿到你需要的数据了。
同意楼上的。
有没有可能是因为你所抓取的网页是空白的啊
你需要加一个前置条件js加载任意网页。网页空白就是没有js,那这个肯定是不能抓取到。
楼上的问题,要看具体网站,如果对一些js完全无感知,比如一些静态站点(大部分是用jquery写的),抓包后得到js是会有框框让它提示js错误的,虽然ie网页抓取插件有个jsinvalidatexss绕过的选项,但是大部分情况下js是不需要显示的,比如@contentdata2d/xxx.js就是直接提示正在加载中,直接禁止执行,不会给服务器提示js错误。
这种方式抓取的只是网页的静态的js文件,apache提供一个nginx,网站抓取的时候加入到php中,提交给php做解析,并不存在图片等内容。如果是在web网站抓取方面,有些web前端和web后端是在一个项目内部写好的,对网站模块不是很懂的可以学一下iis的web配置,一个静态网站全是js。 查看全部
js提取指定网站内容(js提取指定网站内容是什么?怎么做好php提取)
js提取指定网站内容或者是加入到任何一种后端语言的数据库中,比如百度抓取的是搜索引擎的数据,抓取的是数据库里的数据,微信抓取的是网页数据等等。js接收的只是一个字符串,js加在百度搜索页面上的,它把抓取到的网页数据提取出来存入后端语言数据库里,当然能加载进来。并没有什么语言之间的转换,再说这个速度慢到谁也无法接受的程度了。
所以,你抓取数据并不需要把原网页上所有的字符串都转换,不然谁写得出这么丑陋的js库。而是抓取要抓取的网页数据,并加上你需要的数据,然后就可以拿到你需要的数据了。
同意楼上的。
有没有可能是因为你所抓取的网页是空白的啊
你需要加一个前置条件js加载任意网页。网页空白就是没有js,那这个肯定是不能抓取到。
楼上的问题,要看具体网站,如果对一些js完全无感知,比如一些静态站点(大部分是用jquery写的),抓包后得到js是会有框框让它提示js错误的,虽然ie网页抓取插件有个jsinvalidatexss绕过的选项,但是大部分情况下js是不需要显示的,比如@contentdata2d/xxx.js就是直接提示正在加载中,直接禁止执行,不会给服务器提示js错误。
这种方式抓取的只是网页的静态的js文件,apache提供一个nginx,网站抓取的时候加入到php中,提交给php做解析,并不存在图片等内容。如果是在web网站抓取方面,有些web前端和web后端是在一个项目内部写好的,对网站模块不是很懂的可以学一下iis的web配置,一个静态网站全是js。