-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
1705 lines (1625 loc) · 87.6 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://hualiang.online</id>
<title>Hualiang's Blog</title>
<updated>2024-12-10T05:50:00.952Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://hualiang.online"/>
<link rel="self" href="https://hualiang.online/atom.xml"/>
<subtitle><i>Unless I don't want to win, nobody can make me lose.</i><br/><br/>I always believe 天道酬勤</subtitle>
<logo>https://hualiang.online/images/avatar.png</logo>
<icon>https://hualiang.online/favicon.ico</icon>
<rights>All rights reserved 2024, Hualiang's Blog</rights>
<entry>
<title type="html"><![CDATA[关于我脑洞大开去用多线程优化快速排序这件事]]></title>
<id>https://hualiang.online/post/guan-yu-nao-dong-da-kai-qu-yong-duo-xian-cheng-you-hua-kuai-su-pai-xu-zhe-jian-shi/</id>
<link href="https://hualiang.online/post/guan-yu-nao-dong-da-kai-qu-yong-duo-xian-cheng-you-hua-kuai-su-pai-xu-zhe-jian-shi/">
</link>
<updated>2024-10-25T14:50:34.000Z</updated>
<summary type="html"><![CDATA[<p>今晚在复习 TopK 问题手写快排时,突发奇想:“既然快排每次都要划分出两个区间重复进行快排,那么我可不可以将新划分出的两个区间用两个新线程去跑 ? ” 于是就有了这篇文章。</p>
]]></summary>
<content type="html"><![CDATA[<p>今晚在复习 TopK 问题手写快排时,突发奇想:“既然快排每次都要划分出两个区间重复进行快排,那么我可不可以将新划分出的两个区间用两个新线程去跑 ? ” 于是就有了这篇文章。</p>
<!-- more -->
<h1 id="初次尝试">初次尝试</h1>
<p>如果每次划分新区间都开线程跑,那最后的线程数肯定会爆炸式增长,所以我首先想到用线程池去跑。在线程池中,多余的任务放在阻塞队列执行,保证最大执行线程数不超过 CPU 核心数。既能最大利用 CPU 多核,又不至于让线程数溢出,一举两得。</p>
<p>理论可行,开始实践!</p>
<h1 id="代码">代码</h1>
<p>下面是一个原生的快排,我的写法可能跟常规写法不一样,不过效率是一样的:</p>
<pre><code class="language-java">public static void quickSort(int nums, int l, int r) {
if (l >= r) return;
int x = nums[i], i = l, j = r + 1;
while (i < j) {
while (nums[--j] > x && i != j);
if (i == j) nums[j] = x;
else {
nums[i] = nums[j];
while (nums[++i] < x && i != j);
if (i == j) nums[i] = x;
else nums[j] = nums[i];
}
}
quickSort(nums, l, j - 1);
quickSort(nums, j + 1, r);
}
</code></pre>
<p>那如何将线程池用到快排里去呢?其实用栈实现迭代写法会更易于理解。</p>
<p>这里线程池也起到了一个存储任务的作用,即任务队列。每次对区间进行划分后,将划分的区间的左右位置存到队列中,留到之后执行,类似于迭代法中的栈。不过线程池的好处就是,它会自动执行,而不需要我们通过循环去取任务执行。</p>
<p>那么先写一个线程需要执行的方法,我们不需要返回值,所以实现 Runnable,如下:</p>
<pre><code class="language-java">class Task implements Runnable {
private int left;
private int right;
private int[] nums;
private AtomicInteger count;
private ExecutorService executor;
// 传参
public Task(int left, int right, int[] nums, AtomicInteger count, ExecutorService executor) {
this.left = left;
this.right = right;
this.nums = nums;
this.count = count;
this.executor = executor;
}
@Override
public void run() {
// 划分区间前的移位操作
int x = nums[left], i = left, j = right + 1;
while (i < j) {
while (nums[--j] > x && i != j);
if (i == j) nums[j] = x;
else {
nums[i] = nums[j];
while (nums[++i] < x && i != j);
if (i == j) nums[i] = x;
else nums[j] = nums[i];
}
}
if (left < i - 1) {
count.getAndIncrement(); // 将未完成任务数 + 1
// 将下个区间的排序任务交给新线程执行
executor.execute(new Task(left, i - 1, nums, count, executor));
}
if (i + 1 < right) {
count.getAndIncrement();
executor.execute(new Task(i + 1, right, nums, count, executor));
}
count.getAndDecrement(); // 最后记得扣除任务数
}
}
</code></pre>
<p>因为线程需要传参,所以我们通过构造函数给字段赋值来传,这里一个个解释:</p>
<ul>
<li><code>left</code> 和 <code>right</code> :区间左右两边的索引</li>
<li><code>nums</code> :数组</li>
<li><code>count</code> :计数器,用来判断线程池何时将所有任务执行完成</li>
<li><code>executor</code> :线程池</li>
</ul>
<p>接着,我们来写出主类的结构,如下:</p>
<pre><code class="language-java">public class ParallelQuickSort {
public static void main(String[] args) {
// 生成随机数据
int[] nums = generateRandomArray(10000000);
double start = System.currentTimeMillis();
// Arrays.sort(nums);
parallelQuickSort(nums);
double end = System.currentTimeMillis();
System.out.println(((end - start) / 1000) + " seconds");
// 验证排序结果
for (int i = 1; i < nums.length; i++) {
if (nums[i] < nums[i - 1]) {
System.out.println("排序失败!");
break;
}
}
}
public static void parallelQuickSort(int[] nums) {
if (nums == null || nums.length == 0) return;
// 这里图简单,直接用内置线程池
ExecutorService executor = Executors.newFixedThreadPool(20);
// count其实代表了未完成的任务数,包括正在执行和等待执行的
AtomicInteger count = new AtomicInteger(1);
executor.execute(new Task(0, nums.length - 1, nums, count, executor));
// 自旋判断是否已经执行完毕
while (count.get() > 0) {
try {
System.out.println("count:" + count.get());
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 以下皆为关闭线程池的措施
executor.shutdown();
try {
executor.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 随机生成数据
public static int[] generateRandomArray(int size) {
Random random = new Random();
int[] array = new int[size];
for (int i = 0; i < size; i++) {
array[i] = random.nextInt(size) + 1;
}
return array;
}
}
</code></pre>
<p>整个流程其实就是将迭代法中的队列换成了可以自动执行的线程池,而计数器因为存在并发操作,所以使用原子类确保线程安全。</p>
<h1 id="测试">测试</h1>
<p>我们利用随机函数生成随机整型数据,分别使用原生的 <code>Arrays.sort</code> 和我们的多线程快排来测试,结果如下:</p>
<table>
<thead>
<tr>
<th>数据量</th>
<th>Arrays.sort</th>
<th>ParallelQuickSort</th>
</tr>
</thead>
<tbody>
<tr>
<td>10000000</td>
<td>1.466 秒</td>
<td>2.305 秒</td>
</tr>
<tr>
<td>1000000</td>
<td>0.112 秒</td>
<td>0.208 秒</td>
</tr>
</tbody>
</table>
<p>可以看出,我们的多线程竟然比单线程的原生方法还慢,几乎差了一倍,这是什么原因呢?</p>
<p>经过我的一波分析和查阅资料,基本锁定原因:多线程的频繁上下文切换。</p>
<p>在代码中,我们可以看到我并没有对“划分区间给新线程跑”这一行为做限制,以至于即使区间再小也会扔到线程池去执行。而这之间消耗的线程切换时间可要比直接用单线程跑要多。所以我们可以针对这一点进行优化。</p>
<h1 id="二次优化">二次优化</h1>
<p>我们只需要对 <code>run()</code> 作以下修改并且添加一个普通的快排方法即可,经过我测试,当区间长度小于 10000 时直接使用快排效果最好,如下:</p>
<pre><code class="language-java">@Override
public void run() {
int x = nums[left], i = left, j = right + 1;
while (i < j) {
while (nums[--j] > x && i != j);
if (i == j) nums[j] = x;
else {
nums[i] = nums[j];
while (nums[++i] < x && i != j);
if (i == j) nums[i] = x;
else nums[j] = nums[i];
}
}
// 当区间长度小于 10000 时直接使用快排效果
if (right - left <= 10000) {
quicksort(nums, left, i - 1);
quicksort(nums, i + 1, right);
} else {
if (left < i - 1) {
count.getAndIncrement();
executor.execute(new MyRunnable(left, i - 1, nums, count, executor));
}
if (i + 1 < right) {
count.getAndIncrement();
executor.execute(new MyRunnable(i + 1, right, nums, count, executor));
}
}
count.getAndDecrement();
}
public static void quicksort(int[] nums, int l, int r) {
if (l >= r) return;
int x = nums[l], i = l, j = r + 1;
while (i < j) {
while (nums[--j] > x && i != j);
if (i == j) nums[j] = x;
else {
nums[i] = nums[j];
while (nums[++i] < x && i != j);
if (i == j) nums[i] = x;
else nums[j] = nums[i];
}
}
quicksort(nums, l, j - 1);
quicksort(nums, j + 1, r);
}
</code></pre>
<h1 id="最终测试">最终测试</h1>
<p>优化完后,我们再来测试,依旧是五次结果取平均,结果如下:</p>
<table>
<thead>
<tr>
<th>数据量</th>
<th>Arrays.sort</th>
<th>ParallelQuickSort</th>
</tr>
</thead>
<tbody>
<tr>
<td>10000000</td>
<td>1.544 秒</td>
<td>0.339 秒</td>
</tr>
<tr>
<td>1000000</td>
<td>0.111 秒</td>
<td>0.054 秒</td>
</tr>
</tbody>
</table>
<p>可以看出,在千万级数据量下快了将近五倍,只能说成效非常明显。</p>
<h1 id="总结">总结</h1>
<p>最后我思考了一下,<s>为什么原生的快排方法不使用多线程</s>,可能的原因又如下几点:</p>
<ul>
<li>大量数据放在内存中很占空间的,更多会采用多路归并排序(外部排序)</li>
<li>多线程会消耗 CPU 资源,仅仅用来排序感觉多少有点浪费</li>
<li>两者的差距在千万级数据量下才开始有明显差距,大部分情况下不会有这么高</li>
</ul>
<p>p.s. 我后来才知道 Java 的 <code>Arrays.parallelSort()</code> 方法就是原生的多线程快排😂</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Javascript 逆向之 woff 字体反爬破解]]></title>
<id>https://hualiang.online/post/python-pa-chong-js-ni-xiang-zhi-woff-zi-ti-fan-pa-po-jie/</id>
<link href="https://hualiang.online/post/python-pa-chong-js-ni-xiang-zhi-woff-zi-ti-fan-pa-po-jie/">
</link>
<updated>2024-08-25T09:56:36.000Z</updated>
<summary type="html"><![CDATA[<p>转自个人博客 “<a href="https://home.cnblogs.com/u/Eeyhan">Eeyhan</a>” 的<a href="https://www.cnblogs.com/Eeyhan/p/15576450.html">《python爬虫 - js逆向之woff字体反爬破解》</a></p>
]]></summary>
<content type="html"><![CDATA[<p>转自个人博客 “<a href="https://home.cnblogs.com/u/Eeyhan">Eeyhan</a>” 的<a href="https://www.cnblogs.com/Eeyhan/p/15576450.html">《python爬虫 - js逆向之woff字体反爬破解》</a></p>
<!-- more -->
<h1 id="一-前言">一、前言</h1>
<p>本篇博文的主题就是处理字体反爬的,其实这种网上已经很多了,那为什么我还要写呢?因为无聊啊,最近是真没啥事,并且我看了下,还是有点难度的,然后这个字体反爬系列会出两到三篇博文,针对市面上主流的字体反爬,一一讲清楚</p>
<p>不多bb,先看目标站</p>
<blockquote>
<p>http://www.dianping.com/member/79399592/reviews</p>
</blockquote>
<h1 id="二-分析">二、分析</h1>
<p>打开网站发现,如下地址在源码里不显示</p>
<figure data-type="image" tabindex="1"><img src="https://hualiang.online/post-images/1724637148523.png" alt="img" loading="lazy"></figure>
<p>再看下面的文字,网页源码里面也没有正常显示</p>
<figure data-type="image" tabindex="2"><img src="https://hualiang.online/post-images/1724637202451.png" alt="img" loading="lazy"></figure>
<p>这种就很秀了啊,对于没搞过字体反爬的朋友来说,估计就迷糊了,不用怕,跟着我的思路来</p>
<p>先看地址栏,点下那个标签,看右边的css样式(对这个不理解的,看看html前端基础吧,最多一周就懂了),或者看看我的之前的博文,https://www.cnblogs.com/Eeyhan/category/1339041.html</p>
<figure data-type="image" tabindex="3"><img src="https://hualiang.online/post-images/1724637280564.png" alt="img" loading="lazy"></figure>
<p>在看下面的内容:</p>
<figure data-type="image" tabindex="4"><img src="https://hualiang.online/post-images/1724637365331.png" alt="img" loading="lazy"></figure>
<p>这种啥意思呢,首先哈,看到这种源码里面看不到的,那一定是在css样式里,用的@font-face自定义的字体,所以,上面圈出来的两个css就很重要了,点进去看看,点这个</p>
<figure data-type="image" tabindex="5"><img src="https://hualiang.online/post-images/1724637386208.png" alt="img" loading="lazy"></figure>
<p>进去之后,格式化一下,然后就看到如下:</p>
<figure data-type="image" tabindex="6"><img src="https://hualiang.online/post-images/1724637415873.png" alt="img" loading="lazy"></figure>
<p>果然有个@font-face,就看这个后面的url引入了啥样式的字体文件,往后面拉下滚动条,果然看到一个woff的字体文件</p>
<p>补充一下,字体文件格式有哪几种呢?常见的有woff,svg,ttf,其他的就不细说了,好的,先把这个字体下载下来,复制链接浏览器打开直接下载,不用补齐http协议直接下载:</p>
<figure data-type="image" tabindex="7"><img src="https://hualiang.online/post-images/1724637439593.png" alt="img" loading="lazy"></figure>
<p>这个字体先放着,目前这个是地址相关的,再看内容的字体文件,同样的方式点击那个css,进入里面把链接复制出来下载:</p>
<figure data-type="image" tabindex="8"><img src="https://hualiang.online/post-images/1724637462046.png" alt="img" loading="lazy"></figure>
<p>因为我之前分析的时候已经下载过了,所以,文件名会有个(1)。</p>
<p>好的,这两个字体文件,梳理一下,f76的是地址的,924的是内容的,这种文件怎么打开呢?用这个地址:<a href="http://font.qqe2.com/index-en.html">点我</a> ,(百度的在线字体编辑器网址已经打不开了,另外找的一个)在线打开:</p>
<figure data-type="image" tabindex="9"><img src="https://hualiang.online/post-images/1724637481773.png" alt="img" loading="lazy"></figure>
<p>当然你也可以用fontcreator软件打开:</p>
<figure data-type="image" tabindex="10"><img src="https://hualiang.online/post-images/1724637502129.png" alt="img" loading="lazy"></figure>
<p>果然哈,这里面就是定义好的字体了,而可以看到,这种有编码,有实际字体的,只要找到映射关系,就可以把我们要的内容给映射出来了,那么,我们怎么去找映射关系呢?</p>
<p>先看看规律哈,提前说下,这里直接是中文字,而不是网上有些老哥针对字体反爬讲解的数字,然后找到映射关系之后减2哈,所以还是要自己去找那套映射逻辑</p>
<p>怎么找?直接用一个字来看吧,就找这个【广】字</p>
<figure data-type="image" tabindex="11"><img src="https://hualiang.online/post-images/1724637525609.png" alt="img" loading="lazy"></figure>
<p>先看网页源码里这个广是啥编码,好的,<code>&#xe2c9</code>,先放一放</p>
<figure data-type="image" tabindex="12"><img src="https://hualiang.online/post-images/1724637550423.png" alt="img" loading="lazy"></figure>
<p>看这边woff字体里这个广是啥</p>
<p>在线网站看到的,还好,第一页就有,是 <code>unie2c9</code></p>
<figure data-type="image" tabindex="13"><img src="https://hualiang.online/post-images/1724637587200.png" alt="img" loading="lazy"></figure>
<p><code>unie2c9</code> 跟 <code>&#xe2c9</code>,好像有点像,先不急,看下,fontCreator 软件里是啥:</p>
<figure data-type="image" tabindex="14"><img src="https://hualiang.online/post-images/1724637652821.png" alt="img" loading="lazy"></figure>
<p>看着有点不一样哈,这不重要,接下来,我们用 Python 的库看看,有一个大佬写好的字体映射文件库,fontTools(自己用pip安装,不多介绍了)</p>
<figure data-type="image" tabindex="15"><img src="https://hualiang.online/post-images/1724637670683.png" alt="img" loading="lazy"></figure>
<p>打印结果如下,然后它生成了一个 font 的 xml 文件,打开看看:</p>
<figure data-type="image" tabindex="16"><img src="https://hualiang.online/post-images/1724637687037.png" alt="img" loading="lazy"></figure>
<p>里面有两个关键的节点就是 <code>GlyphOrder</code> 和 <code>cmap</code>,而这两个,刚才的代码里已经打印出来了,结果:</p>
<figure data-type="image" tabindex="17"><img src="https://hualiang.online/post-images/1724637734981.png" alt="img" loading="lazy"></figure>
<p>那行,我们找下这个【广】在哪,搜从在线字体文件编辑网里拿到的 <code>unie2c9</code>,发现有两个:</p>
<figure data-type="image" tabindex="18"><img src="https://hualiang.online/post-images/1724637753741.png" alt="img" loading="lazy"></figure>
<figure data-type="image" tabindex="19"><img src="https://hualiang.online/post-images/1724637776491.png" alt="img" loading="lazy"></figure>
<p>哪个才是呢?再搜下,字体文件拿到的 <code>glyph86</code>,发现没有</p>
<figure data-type="image" tabindex="20"><img src="https://hualiang.online/post-images/1724638532426.png" alt="img" loading="lazy"></figure>
<p>但是,目前感觉有点联系,<code>&#xe2c9</code> - <code>unie2c9</code> - <code>86</code></p>
<p>这种是啥呀,就不多说了,<code>unie2c9</code> 前面的 <code>uni</code> 就是 unicode 编码的意思,姑且认定为 &<code>#xe2c9</code> = <code>unie2c9</code> ,那 86 呢,怎么映射出【广】字的,大胆猜测,这个 86 就是索引位置,在那个 woff 文件里数一下,看是不是第 86 个,先看这个,一行是 10 个,然后第一行是没有任何编码的,所以第一行只有 9 个,</p>
<figure data-type="image" tabindex="21"><img src="https://hualiang.online/post-images/1724637801401.png" alt="img" loading="lazy"></figure>
<p>往下数,数到第8行倒数第四个,也就是87,但是第一行只有9个,那就是86了</p>
<figure data-type="image" tabindex="22"><img src="https://hualiang.online/post-images/1724637916709.png" alt="img" loading="lazy"></figure>
<p>哈哈哈,刚好对上,那现在就说得通了,那我们先拿到源码,然后去找映射关系,找到索引位置,再从索引位置里找到真实的文字内容就行了。</p>
<p>但有个很繁琐的,这些实际的文字内容,我们要一个一个的手写映射关系(哭了),没法啊,找好之后,写成一个 json,然后 load 吧</p>
<figure data-type="image" tabindex="23"><img src="https://hualiang.online/post-images/1724638148671.png" alt="img" loading="lazy"></figure>
<h1 id="三-调试">三、调试</h1>
<p>先把刚才打开网页源码,直接copy到本地保存成html文件测试吧,免得一改什么就请求下,因为这个站的风控还挺强的</p>
<p>废话不多说,直接处理保存在本地的html,然后我只打印了地址信息</p>
<figure data-type="image" tabindex="24"><img src="https://hualiang.online/post-images/1724638167364.png" alt="img" loading="lazy"></figure>
<p>感觉跟在源码里看到的&#开头的有点不一样,好像给处理成了【\u】,先看看能不能处理吧:</p>
<p>复制一个 <code>['\ue2c9', '\uef20', '\ue801', '5', '\ued77', '\ue150', '42']</code> ,拿来处理下,</p>
<figure data-type="image" tabindex="25"><img src="https://hualiang.online/post-images/1724638205464.png" alt="img" loading="lazy"></figure>
<p>卧槽,这咋回事,打断点一看,这个参数并不是我们预期的,</p>
<figure data-type="image" tabindex="26"><img src="https://hualiang.online/post-images/1724638222729.png" alt="img" loading="lazy"></figure>
<p>那多半就是那个被转义成【\u】的问题了,那我们直接在读取内容的时候,直接就替换一下:</p>
<figure data-type="image" tabindex="27"><img src="https://hualiang.online/post-images/1724638245820.png" alt="img" loading="lazy"></figure>
<p>执行下:</p>
<figure data-type="image" tabindex="28"><img src="https://hualiang.online/post-images/1724638266448.png" alt="img" loading="lazy"></figure>
<p>然后同样的,拿第一个来处理:</p>
<figure data-type="image" tabindex="29"><img src="https://hualiang.online/post-images/1724638286373.png" alt="img" loading="lazy"></figure>
<p>完美,跟原网站的数据对上</p>
<figure data-type="image" tabindex="30"><img src="https://hualiang.online/post-images/1724638302887.png" alt="img" loading="lazy"></figure>
<p>接着再处理内容的,这个内容原理一样,只是把woff文件替换下即可</p>
<p>打印下内容的:</p>
<figure data-type="image" tabindex="31"><img src="https://hualiang.online/post-images/1724638321482.png" alt="img" loading="lazy"></figure>
<p>选第一个,然后执行:</p>
<figure data-type="image" tabindex="32"><img src="https://hualiang.online/post-images/1724638339615.png" alt="img" loading="lazy"></figure>
<p>对比原网站:</p>
<figure data-type="image" tabindex="33"><img src="https://hualiang.online/post-images/1724638355148.png" alt="img" loading="lazy"></figure>
<p>然后,有朋友要问了,那后面的emoji怎么没有搞出来,看看源码哈:</p>
<figure data-type="image" tabindex="34"><img src="https://hualiang.online/post-images/1724638369168.png" alt="img" loading="lazy"></figure>
<p>这个emoji,是个图片资源,你要处理肯定是可以的,拼接一下就可以了</p>
<h1 id="四-python-实现">四、Python 实现</h1>
<p>提一句,那两个字体文件经过我的发现,是会不定期变的,所以你需要去请求源码,用正则匹配指定位置,然后请求css文件,再去把woff文件url匹配出来,单独请求,下载下来,接着完成后续的工作即可</p>
<p>最后用 Python 完整实现,完整的代码就不贴出来了,后续的都是一些常规且简单的操作了,再一个就是,我根本就没写完整的代码(哈哈哈哈哈),只贴出部分:</p>
<pre><code class="language-python">from fontTools.ttLib import TTFont
import re
import requests
from lxml import etree
import json
def parser_woff_font(font='4375cf76.woff', something=None):
font = TTFont(font)
glyph = font.getReverseGlyphMap()
f = open('font_template.json', encoding='utf-8')
font_template = json.load(f)
f.close()
new_str = ''
for item in something:
if not item:
continue
if item.endswith(';'):
item = item.replace(';', '')
if item in glyph:
index = glyph.get(item)
if index:
real = font_template.get(str(index))
if real:
new_str += real
else:
new_str += item
print(12312312, new_str)
return new_str
def get_real_data():
f = open('content.html', encoding='utf-8')
source_data = f.read()
source_data = source_data.replace('&#x', 'uni')
f.close()
html = etree.HTML(source_data)
data = html.xpath('//div[@class="txt J_rptlist"]')
for item in data:
temp_dict = dict()
shop_name = item.xpath('./div[1]/h6//text()')
shop_addr = item.xpath('.//div[@class="mode-tc addres"]/p//text()')
shop_score = item.xpath('.//div[@class="mode-tc comm-rst"]/span/@class')
shop_comment = item.xpath('.//div[@class="mode-tc comm-entry"]//text()')
comment_photo_url = item.xpath('.//div[@class="mode-tc comm-photo"]/a/@href')
comment_photo_url = ''.join(comment_photo_url) if comment_photo_url else ''
create_time = item.xpath('.//div[@class="mode-tc info"]/span[1]/text()')
create_time = ''.join(create_time) if create_time else ''
if create_time:
create_time = create_time.replace('发表于', '')
temp_dict['shop_name'] = shop_name
temp_dict['shop_addr'] = shop_addr
temp_dict['shop_score'] = shop_score
temp_dict['shop_comment'] = shop_comment
temp_dict['comment_photo_url'] = comment_photo_url
temp_dict['create_time'] = create_time
print(123123, temp_dict['shop_comment'])
# get_real_data()
s = ['unif1af;', 'unif147;', 'uniecc0;', 'unie635;', 'unif083;', 'unie3c5;', 'unif802;', ' ', 'unie931;', 'uniea55;', 'unif534;', 'unied79;', 'unie1bd;', ' ', 'unie1e4;', 'unie7b0;', 'unie65d;', 'unif534;', 'unie3c5;', 'unie66f;', 'unif52d;', ' ', 'unif765;', 'unif49d;', 'unieb19;', 'unie2de;', 'unie66f;', '闹', 'unie8ee;', 'unie3a4;', 'unif759;', ' ', 'unif195;', 'unif195;', 'unif195;', 'unif195;']
parser_woff_font('2f66e924.woff', s)
</code></pre>
<p>那个映射的font_template.json文件,<a href="https://files.cnblogs.com/files/Eeyhan/font_template.json">点我</a></p>
<p>说明一下,这个json映射关系是只针对这一个站,并不通用网上所有的字体反爬哈,而且,这个站的映射,说不定以后还会改变,所以,你懂我意思吧</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[记一次对 DataX 向 ES 同步对象型数据的探索过程]]></title>
<id>https://hualiang.online/post/ji-yi-ci-dui-datax-xiang-es-tong-bu-dui-xiang-xing-shu-ju-de-jie-jue-guo-cheng/</id>
<link href="https://hualiang.online/post/ji-yi-ci-dui-datax-xiang-es-tong-bu-dui-xiang-xing-shu-ju-de-jie-jue-guo-cheng/">
</link>
<updated>2024-08-12T08:02:10.000Z</updated>
<summary type="html"><![CDATA[<p>今天开完产品审议会,Leader 表示让我来负责工作台模块自定义地理查询的功能开发时,已经学完 ES 的地理查询的我当即表示莫得问题👌。可就在我想先同步坐标在写业务代码时才发现,“这个 DataX 怎么同步对象型数据嘞?”🤔</p>
]]></summary>
<content type="html"><![CDATA[<p>今天开完产品审议会,Leader 表示让我来负责工作台模块自定义地理查询的功能开发时,已经学完 ES 的地理查询的我当即表示莫得问题👌。可就在我想先同步坐标在写业务代码时才发现,“这个 DataX 怎么同步对象型数据嘞?”🤔</p>
<!-- more -->
<h1 id="介绍">介绍</h1>
<p>DataX 是阿里巴巴开源的一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。使用插件增强功能,这里我需要从 MySQL 同步数据到 ES,就要用到 MySQL 的输入端插件和 ES 的输出端插件。</p>
<h1 id="用法">用法</h1>
<p>这里演示从 MySQL 同步到 ES,DataX 的同步很简单,只需要一个配置脚本并运行下面地命令即可</p>
<pre><code>python /datax/bin/datax.py job.json
</code></pre>
<p>DataX 就会自动按照配置文件里的信息连接对应服务获取和同步数据。示例如下:</p>
<pre><code class="language-json">// job.json
{
"job": {
"setting": {
"speed": {
"channel": 2
}
},
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "root",
"password": "123456",
"connection": [
{
"querySql": ["select * from user_t"],
"jdbcUrl": ["jdbc:mysql://127.0.0.1:3306/db_user"]
}
]
}
},
"writer": {
"name": "elasticsearchwriter",
"parameter": {
"endpoint": "http://127.0.0.1:9200",
"accessId": "elastic",
"accessKey": "123456",
"index": "user",
"column": [
{ "name": "pk", "type": "id" },
{ "name": "col_ip", "type": "ip" },
{ "name": "col_double", "type": "double" },
{ "name": "col_long", "type": "long" },
{ "name": "col_integer", "type": "integer" },
{ "name": "col_keyword", "type": "keyword" },
{ "name": "col_text", "type": "text", "analyzer": "ik_max_word" },
{ "name": "col_geo_point", "type": "geo_point" },
{ "name": "col_date", "type": "date", "format": "yyyy-MM-dd HH:mm:ss" },
{ "name": "col_nested1", "type": "nested" },
{ "name": "col_nested2", "type": "nested" },
{ "name": "col_object1", "type": "object" },
{ "name": "col_object2", "type": "object" },
{ "name": "col_integer_array", "type": "integer", "array": true },
{ "name": "col_geo_shape", "type": "geo_shape", "tree": "quadtree", "precision": "10m" }
]
}
}
}
]
}
}
</code></pre>
<h1 id="问题">问题</h1>
<p>看起来是不是很简单?如果只是针对普通字符串,数字,或是日期字段,那确实没啥问题,通过 SQL 语句 都能正确地查出并同步。但问题就出在最后一个字段 <code>col_geo_shape</code>,它的类型是 <code>geo_shape</code>。了解 ES 的人都知道,这是 ES 用于地理查询的一个重要类型,用它我们可以实现判断坐标和坐标,坐标和区域以及区域和区域之间的空间关系。</p>
<p>但问题是,在 ES 中,geo_shape 类型的数据长下面这样:</p>
<pre><code class="language-json">"col_geo_shape": {
"type": "point",
"coordinates": [
108.374854,
30.809156
]
}
</code></pre>
<p>没错,它是一个遵循 GeoJson 格式的 Json 对象,这里的类型除了 <code>point</code> 外,还有 <code>circle</code>,<code>envelope</code>,<code>polygon</code> 等等</p>
<p>对于 SQL 语句的查询结果,我们都知道是一个个字段,默认情况下,它们都可以作为字符串,也就是对应 ES 的 <code>keyword</code> 或 <code>text</code> 类型。但 SQL 如何查出一个对象?</p>
<p>我相信到多数人的第一反应拼出来一个 json 串。如果你想到了,那么恭喜你答对了!可惜的是,当时的我并没有那么聪明,再加上每一次测试同步数据都要等很长的时间,我可不想为了一个猜测去冒这么大的时间成本(其实就是想追求一次通过)</p>
<p>于是,漫长的探索过程就开始了......</p>
<h1 id="探索">探索</h1>
<p>原本我以为这样一个小问题,广大网友应该已经踩过坑了,大不了官方文档应该有写怎么用吧。</p>
<p>但直到我搜索了无数次,就是没有找到向 ES 同步对象型数据的具体示例。大部分都是在同步普通类型,如:<code>integer</code>,<code>date</code>,<code>keyword</code>,<code>long</code> 等等。还有一些介绍也只是提了一嘴能同步 <code>geo_shape</code> 类型,但却依然没有给出具体的示例。</p>
<p>这时,我想到,既然官方有提供这样一个类型,那么一定有同步它的办法。所以我决定去 GitHub 找找<a href="https://github.com/alibaba/DataX/blob/master/elasticsearchwriter/doc/elasticsearchwriter.md">官方文档</a>。令我大失所望的是,官方仅提供了 ES 输出端的配置信息,我仍不清楚 mysql 到底怎么同步对象给 ES。</p>
<p>最终,我决定看源码。</p>
<h1 id="分析">分析</h1>
<p>经过一番寻找,我最终找到了相关的代码,位于 <a href="https://github.com/alibaba/DataX/blob/master/elasticsearchwriter/src/main/java/com/alibaba/datax/plugin/writer/elasticsearchwriter/ElasticSearchWriter.java#L301">ElasticSearchWriter.java</a> 如下:</p>
<pre><code class="language-java">// ElasticSearchWriter.java
switch (colType) {
case STRING:
// 兼容string类型,ES5之前版本
break;
case KEYWORD:
// https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html#_warm_up_global_ordinals
field.put("eager_global_ordinals", jo.getBoolean("eager_global_ordinals"));
break;
case TEXT:
field.put("analyzer", jo.getString("analyzer"));
// 优化disk使用,也同步会提高index性能
// https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-disk-usage.html
field.put("norms", jo.getBoolean("norms"));
field.put("index_options", jo.getBoolean("index_options"));
if(jo.getString("fields") != null) {
field.put("fields", jo.getJSONObject("fields"));
}
break;
case DATE:
if (Boolean.TRUE.equals(jo.getBoolean("origin"))) {
if (jo.getString("format") != null) {
field.put("format", jo.getString("format"));
}
// es原生format覆盖原先来的format
if (jo.getString("dstFormat") != null) {
field.put("format", jo.getString("dstFormat"));
}
if(jo.getBoolean("origin") != null) {
columnItem.setOrigin(jo.getBoolean("origin"));
}
} else {
columnItem.setTimeZone(jo.getString("timezone"));
columnItem.setFormat(jo.getString("format"));
}
break;
case GEO_SHAPE:
field.put("tree", jo.getString("tree"));
field.put("precision", jo.getString("precision"));
break;
case OBJECT:
case NESTED:
if (jo.getString("dynamic") != null) {
field.put("dynamic", jo.getString("dynamic"));
}
break;
default:
break;
}
if (jo.containsKey("other_params")) {
field.putAll(jo.getJSONObject("other_params"));
}
</code></pre>
<p>注意看最后一个 if 语句,它会将除固定配置外的其余参数当成 json 串进行反序列化。这也坐实了,你只需要在 SQL 语句中通过 <code>CONCAT</code> 等字符串函数拼出一个字符串即可同步对象型数据。</p>
<h1 id="结尾">结尾</h1>
<p>总之,这件事给我的启发就是:<strong>在行动前一定要做好可行性分析</strong>,否则到时候代码写一半发现方法行不通,那就白写了🤣</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[记一次 Wrangler CLI 创建 Cloudflare Worker 模板启动失败原因]]></title>
<id>https://hualiang.online/post/ji-yi-ci-wrangler-cli-chuang-jian-cloudflare-worker-mo-ban-qi-dong-shi-bai-yuan-yin/</id>
<link href="https://hualiang.online/post/ji-yi-ci-wrangler-cli-chuang-jian-cloudflare-worker-mo-ban-qi-dong-shi-bai-yuan-yin/">
</link>
<updated>2024-08-04T07:46:57.000Z</updated>
<summary type="html"><![CDATA[<p>因为想用 Worker API 绑定 Cloudflare R2 便于使用,所有遵循<a href="https://developers.cloudflare.com/r2/api/workers/workers-api-usage/">官方文档</a>创建模板。</p>
<p>但是遇上了一个莫名其妙的 Bug 报错...</p>
]]></summary>
<content type="html"><![CDATA[<p>因为想用 Worker API 绑定 Cloudflare R2 便于使用,所有遵循<a href="https://developers.cloudflare.com/r2/api/workers/workers-api-usage/">官方文档</a>创建模板。</p>
<p>但是遇上了一个莫名其妙的 Bug 报错...</p>
<!-- more -->
<h1 id="报错过程">报错过程</h1>
<p>使用脚手架创建模板</p>
<pre><code class="language-shell">npm create cloudflare@latest r2-worker
</code></pre>
<p>按步骤创建好后,运行 <code>npm run dev</code> 遭遇一下报错:</p>
<figure data-type="image" tabindex="1"><img src="https://hualiang.online/post-images/1722760104914.png" alt="Bug" loading="lazy"></figure>
<p>通过查看日志文件详细信息如下:</p>
<pre><code>--- 2024-06-30T19:28:07.425Z debug
🪵 Writing logs to "C:\Users\...\.wrangler\logs\wrangler-2024-06-30_19-28-07_302.log"
---
--- 2024-06-30T19:28:07.425Z debug
Failed to load .env file ".env": Error: ENOENT: no such file or directory, open 'C:\Cycles of Seasons\100 - Virtues\110 - Demiourgia\test-wrangler\hello-world-js\.env'
at Object.openSync (node:fs:573:18)
at Object.readFileSync (node:fs:452:35)
at tryLoadDotEnv (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:158768:72)
at loadDotEnv (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:158777:12)
at C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:202740:20
at C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:162911:16
at maybeAsyncResult (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:161132:44)
at C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:162910:14
at C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:161119:22
at Array.reduce (<anonymous>) {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'C:\\...\\test-wrangler\\hello-world-js\\.env'
}
---
--- 2024-06-30T19:28:07.432Z log
⛅️ wrangler 3.62.0
[38;2;255;136;0m-------------------[39m
---
--- 2024-06-30T19:28:07.450Z debug
Metrics dispatcher: Posting data {"type":"event","name":"run dev","properties":{"local":true,"usesTypeScript":false}}
---
--- 2024-06-30T19:28:07.455Z debug
Failed to load .env file "C:\...\test-wrangler\hello-world-js\.dev.vars": Error: ENOENT: no such file or directory, open 'C:\...\test-wrangler\hello-world-js\.dev.vars'
at Object.openSync (node:fs:573:18)
at Object.readFileSync (node:fs:452:35)
at tryLoadDotEnv (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:158768:72)
at loadDotEnv (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:158777:12)
at getVarsForDev (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:200152:18)
at getBindings (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:207740:10)
at getBindingsAndAssetPaths (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:207621:20)
at getDevReactElement (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:207276:40)
at startDev (C:\...\test-wrangler\hello-world-js\node_modules\wrangler\wrangler-dist\cli.js:207343:60)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'C:\\...\\test-wrangler\\hello-world-js\\.dev.vars'
}
---
--- 2024-06-30T19:28:07.571Z log
[2m⎔ Starting local server...[22m
---
--- 2024-06-30T19:28:07.580Z debug
*** Received structured exception #0xc0000005: access violation; stack: 7ffcf4ed2f57 7ff7e9e1643b 7ff7e9e16503 7ff7e9e0588c 7ff7e9e05837 7ff7e9649c1e 7ff7e9649f2f 7ff7e8531ad6 7ff7e85318ba 7ff7e97cb6ef 7ff7e97d28a6 7ff7e97cbc0c 7ff7e97d28a6 7ff7e97c957c 7ff7e8521551 7ff7eaf00f7f 7ffd07c2257c 7ffd0824af27
---
--- 2024-06-30T19:28:07.632Z debug
*** Received structured exception #0xc0000005: access violation; stack: 7ffcf4ed2f57 7ff7e9e1643b 7ff7e9e16503 7ff7e9e0588c 7ff7e9e05837 7ff7e9649c1e 7ff7e9649f2f 7ff7e8531ad6 7ff7e85318ba 7ff7e97cb6ef 7ff7e97d28a6 7ff7e97cbc0c 7ff7e97d28a6 7ff7e97c957c 7ff7e8521551 7ff7eaf00f7f 7ffd07c2257c 7ffd0824af27
---
</code></pre>
<p>明显可以看到因缺少 <code>.env</code> 和 <code>.dev.vars</code> 报错,但这不是重点,即使添加了文件依旧报错。</p>
<p>问题出在这:</p>
<pre><code>--- 2024-06-30T19:28:07.580Z debug
*** Received structured exception #0xc0000005: access violation; stack: 7ffcf4ed2f57 7ff7e9e1643b 7ff7e9e16503 7ff7e9e0588c 7ff7e9e05837 7ff7e9649c1e 7ff7e9649f2f 7ff7e8531ad6 7ff7e85318ba 7ff7e97cb6ef 7ff7e97d28a6 7ff7e97cbc0c 7ff7e97d28a6 7ff7e97c957c 7ff7e8521551 7ff7eaf00f7f 7ffd07c2257c 7ffd0824af27
---
--- 2024-06-30T19:28:07.632Z debug
*** Received structured exception #0xc0000005: access violation; stack: 7ffcf4ed2f57 7ff7e9e1643b 7ff7e9e16503 7ff7e9e0588c 7ff7e9e05837 7ff7e9649c1e 7ff7e9649f2f 7ff7e8531ad6 7ff7e85318ba 7ff7e97cb6ef 7ff7e97d28a6 7ff7e97cbc0c 7ff7e97d28a6 7ff7e97c957c 7ff7e8521551 7ff7eaf00f7f 7ffd07c2257c 7ffd0824af27
---
</code></pre>
<p>这个报错有点不明所以,只能去官方 Github 仓库 <a href="https://github.com/cloudflare/workers-sdk">woker-sdk</a> 下的 issues 寻找解决方法。</p>
<h1 id="解决方法">解决方法</h1>
<p>经过一番查找,最终在 <a href="https://github.com/cloudflare/workers-sdk/issues/6170">#6170</a> 下找到了解决方法。</p>
<p>该报错可能是由于电脑的 Microsoft Visual C++ 与 wrangler 的依赖包版本不兼容导致的,大多发生在 Windows 11 系统,所以有两种解决方法。</p>
<hr>
<h2 id="使用更低版本的-wrangler">使用更低版本的 wrangler</h2>
<p>经测试,使用 3.57.1 版本的 wrangler,可以正常运行。可以直接运行下面的命令来降级:</p>
<pre><code class="language-shell">npm uninstall wrangler && npm install [email protected] -D
</code></pre>
<p>不过该方法指标不治本,只能用于临时应急,不太推荐。</p>
<hr>
<h2 id="更新-microsoft-visual-c-到最新版本">更新 Microsoft Visual C++ 到最新版本</h2>
<p>wrangler 引用的是下面两个版本的库,所以我们只需要更新它们即可。</p>
<figure data-type="image" tabindex="2"><img src="https://hualiang.online/post-images/1722759393446.png" alt="Microsoft Visual C++" loading="lazy"></figure>
<p>图中是 wrangler 能够正常运行的版本,也是我现在的最新版本,建议更新时选择该版本及以上版本。</p>
<p>这里推荐直接去“<a href="https://learn.microsoft.com/zh-cn/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version">官方下载地址</a>”下载最新软件包更新。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[ElasticSearch 实现地理位置搜索]]></title>
<id>https://hualiang.online/post/elasticsearch-java-api-shi-xian-di-li-wei-zhi-cha-xun/</id>
<link href="https://hualiang.online/post/elasticsearch-java-api-shi-xian-di-li-wei-zhi-cha-xun/">
</link>
<updated>2024-08-03T02:34:55.000Z</updated>
<summary type="html"><![CDATA[<p>最近,实习时涉及到了在地图上显示客户锚点的需求,想到 ES 有这么个功能可以用,便想来试试。但网上的教程太少了,我自己也是琢磨半点才看懂的,在此分享一下。</p>
]]></summary>
<content type="html"><![CDATA[<p>最近,实习时涉及到了在地图上显示客户锚点的需求,想到 ES 有这么个功能可以用,便想来试试。但网上的教程太少了,我自己也是琢磨半点才看懂的,在此分享一下。</p>
<!-- more -->
<h1 id="创建映射和文档">创建映射和文档</h1>
<p>ES 的地理位置类型分为 <code>geo_point</code> 和 <code>geo_shape</code> 两种。前者表示一个地图上的点,即坐标;后者则表示多个点框出的一篇区域。功能上讲,后者的功能更强。</p>
<p>我们先创建一个带有这两种类型的索引映射:</p>
<pre><code class="language-json">{
"properties": {
"location": {
"type": "geo_point"
},
"area": {
"type": "geo_shape"
}
}
}
</code></pre>
<h2 id="geo_point">geo_point</h2>
<p>对于 geo_point 类型插入文档很简单,有三种方法,如下:</p>
<pre><code class="language-json">"location": "34.247232,108.945872" // 第一种,直接插入该格式的字符串
"location": [108.945872, 34.247232] // 第二种,可以用数组 [lon, lat] 的形式表示
"location": { // 第三种,以对象形式插入
"lat": 34.247232,
"lon": 108.945872
}
</code></pre>
<h2 id="geo_shape">geo_shape</h2>
<p>对于 geo_shape 类型插入文档较复杂,因为它有很多子类型,如,<code>point</code>,<code>circle</code>,<code>envelope</code>,<code>linestring</code>,<code>polygon</code>,<code>multipoint</code>,<code>multilinestring</code>,<code>multipolygon</code> 等。</p>
<p>下面就介绍几种常用的类型:</p>
<pre><code class="language-json">"area": {
"type": "point", // 点
"coordinates": [108.945872, 34.247232]
}
"area": {
"type": "circle", // 圆
"radius": "10km",
"coordinates": [-74.0059, 40.7128]
}
"area": {
"type": "envelope", // 矩形
"coordinates" : [
[108.945872, 34.247232],
[108.374854, 30.809156]
]
}
"area": {
"type": "linestring", // 线,至少两个点
"coordinates": [
[108.945872, 34.247232],
[108.374854, 30.809156],
[108.378368, 30.809938]
]
}
"area": {
"type": "polygon", // 封闭多边形,其首点和末点必须匹配,最少需要 4 个顶点
"coordinates": [
[ // 第一个多边形,作为主体
[-77.03653, 38.897676],
[-77.03653, 37.897676],
[-76.03653, 38.897676],
[-77.03653, 38.997676],
[-77.03653, 38.897676]
]
// 若存在第二个及以后的多边形,则作为主体中的“洞”,排除主体中不需要包含的面积
]
}
</code></pre>
<p>其余的 multi 类型就是在外围多加一个中括号即可。</p>
<h1 id="地理位置搜索">地理位置搜索</h1>
<p>geo_point 的查询方式与 geo_shape 不同,两者常用的查询方式有半径,矩形和多边形查询。但 geo_shape 查询可以兼容 geo_point 类型,而且 geo_shape 不仅可以搜索选定区域的点,还可以搜索区域,查询的空间关系如下:</p>
<ul>
<li>INTERSECTS -(默认)返回其 geo_shape 或 geo_point 字段与查询几何相交的所有文档。</li>
<li>DISJOINT - 返回其 geo_shape 或 geo_point 字段与查询几何没有共同点的所有文档。</li>
<li>WITHIN - 返回其 geo_shape 或 geo_point 字段在查询几何内的所有文档。 不支持线几何。</li>
<li>CONTAINS - 返回其 geo_shape 或 geo_point 字段包含查询几何的所有文档。</li>
</ul>
<h2 id="半径搜索">半径搜索</h2>
<p>geo_point 的半径搜索就是在地图上标定一个中心点,再标出半径,查询在这个圆内的坐标点。</p>
<pre><code class="language-json">{
"query": {
"geo_distance": {
"distance": "500km", // 半径,可以附带单位
"location": { // 中心点,此处使用的是第三种写法
"lat": "38.993443",
"lon": "117.158558"
}
}
}
}
</code></pre>
<p>geo_shape 也是类似,不过它跟插入文档时的格式一样。</p>
<pre><code class="language-json">{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "circle",
"radius": "10km",
"coordinates": [-74.0059, 40.7128]
}
}
}
}
}
</code></pre>
<h2 id="矩形搜索">矩形搜索</h2>
<p>geo_point 的矩形搜索只要给出左上角和右下角两个坐标即可。</p>
<pre><code class="language-json">{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 47.7328,
"lon": -122.448
},
"bottom_right": {
"lat": 47.468,
"lon": -122.0924
}
}
}
}
}
</code></pre>
<p>geo_shape 也是类似,不过它跟插入文档时的格式一样。</p>
<pre><code class="language-json">{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "envelope", // 矩形
"coordinates" : [
[108.945872, 34.247232],
[108.374854, 30.809156]
]
}
}
}
}
}
</code></pre>
<h2 id="多边形搜索">多边形搜索</h2>
<p>geo_point 的多边形搜索需要给出组成多边形的所有边界点。</p>
<pre><code class="language-json">{
"query": {
"geo_polygon": {
"location": {
"points" : [
{"lat" : 40, "lon" : -70},
{"lat" : 30, "lon" : -80},
{"lat" : 20, "lon" : -90}
]
}
}
}
}
</code></pre>
<p><strong>注意</strong>:geo_point 的多边形,其首点和末点是无需匹配的,而 geo_shape 的必须要匹配。</p>
<p>geo_shape 也是类似,不过它跟插入文档时的格式一样。</p>
<pre><code class="language-json">{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "polygon",
"coordinates": [
[
[-77.03653, 38.897676],
[-77.03653, 37.897676],
[-76.03653, 38.897676],
[-77.03653, 38.997676],
[-77.03653, 38.897676]
]
]
}
}
}
}
}
</code></pre>
<h1 id="java-api-实现地理位置搜索">Java API 实现地理位置搜索</h1>
<p>ElasticSearch 提供了一套 API 给 Java 用于操作,需要引入下面的依赖:</p>
<pre><code class="language-xml"><dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.15.2</version>
</dependency>
</code></pre>
<p>因为业务场景中,ES 中的数据大多是从其他数据源同步过来的,而非用 Java 手动创建,所以下面仅介绍如何实现地理位置搜索。</p>
<p>首先,搭建好框架,便于测试:</p>
<pre><code class="language-java">@SuppressWarnings("deprecation")
public class ESTest_Doc_Geo_Query {
public static final double[][][][] coordinates = {
{
{
{ 116.53, 39.67 },
{ 117.05, 39.67 },
{ 116.39, 39.42 },
{ 117.48, 39.16 },
{ 116.53, 39.67 }
}
},
{
{
{ 116.53, 39.67 },
{ 117.05, 39.67 },
{ 116.39, 39.42 },
{ 117.48, 39.16 },
{ 116.53, 39.67 }
}
}
};
public static void main(String[] args) {
RestClientBuilder builder = RestClient.builder(new HttpHost("127.0.0.1", 9200, "http"));
try (RestHighLevelClient client = new RestHighLevelClient(builder)) {
// 接下来,只需要调用不同的方法就行实现不同的搜索
QueryBuilder geoShapeQuery = geoShapePolygonQuery(coordinates);
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().must(QueryBuilders.matchAllQuery())
.filter(geoShapeQuery);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery);
SearchRequest request = new SearchRequest().indices("geo").source(searchSourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
System.out.println(response.getTook());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
</code></pre>
<p>因为半径搜索和多边形搜索更常用,所以就不介绍矩形搜索了,感兴趣自行搜索。</p>
<h2 id="半径搜索-2">半径搜索</h2>
<p>我们创建两个方法 <code>geoPointCircleQuery</code>,<code>geoShapeCircleQuery</code> 来代表两种类型的搜索:</p>
<pre><code class="language-java">public static GeoDistanceQueryBuilder geoPointCircleQuery(String name, double lon, double lat, String distance) {
return QueryBuilders.geoDistanceQuery(name).distance(distance).point(lat, lon);
}
public static GeoShapeQueryBuilder geoShapeCircleQuery(String name, double lon, double lat, double radius) throws IOException {
return QueryBuilders.geoIntersectionQuery(name, new Circle(lon, lat, radius * 1000));
}
</code></pre>
<p>geo_point 的每种搜索都会有一个专门的 Builder 类,而 geo_shape 只有一种。</p>
<p><code>geoIntersectionQuery</code> 等价于 使用 <code>builder.relation(ShapeRelation.INTERSECTS)</code> 设置空间关系为<strong>相交</strong>的 <code>geoShapeQuery</code>。同理,其余关系也有专门的查询类。当然,你也可以选择手动设置。</p>
<h2 id="多边形搜索-2">多边形搜索</h2>
<p>同上,还是封装两个方法实现:</p>
<pre><code class="language-java">public static GeoPolygonQueryBuilder geoPointPolygonQuery(String name, double[][] points) {
List<GeoPoint> geoPoints = Arrays.stream(points).map(point -> new GeoPoint(point[1], point[0]))
.collect(Collectors.toList());
return QueryBuilders.geoPolygonQuery(name, geoPoints);
}
public static GeoShapeQueryBuilder geoShapePolygonQuery(String name, double[][] points) throws IOException {
double[] lat = Arrays.stream(points).mapToDouble(point -> point[1]).toArray();
double[] lon = Arrays.stream(points).mapToDouble(point -> point[0]).toArray();
return QueryBuilders.geoIntersectionQuery(name, new Polygon(new LinearRing(lon, lat)));
}
</code></pre>
<p><code>LinearRing</code> 代表一个闭合的线,仅作为创建 <code>Polygon</code> 的边界,不能直接应用于搜索。</p>
<p><em>p.s. <code>Polygon</code> 还提供了 <code>Polygon(LinearRing polygon, List<LinearRing> holes)</code> 的构造方法来创建具有“洞”的多边形。</em></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[如何用超星云盘做外链直链]]></title>
<id>https://hualiang.online/post/ru-he-yong-chao-xing-yun-pan-zuo-wai-lian-zhi-lian/</id>
<link href="https://hualiang.online/post/ru-he-yong-chao-xing-yun-pan-zuo-wai-lian-zhi-lian/">
</link>
<updated>2024-06-29T04:56:19.000Z</updated>
<summary type="html"><![CDATA[<p>转自个人博客 “<a href="https://zxz.ee">小言u</a>” 的<a href="https://zxz.ee/100.html">《用超星云盘做外链直链》</a></p>
]]></summary>
<content type="html"><![CDATA[<p>转自个人博客 “<a href="https://zxz.ee">小言u</a>” 的<a href="https://zxz.ee/100.html">《用超星云盘做外链直链》</a></p>
<!-- more -->
<h1 id="注意">注意</h1>
<p>仅是尝试阶段,具体什么时候会时效我也不知道,仅供参考使用,如果想要真正的投入到日常使用还是建议选择购买对象存储。</p>
<h1 id="图片方法步骤">图片方法步骤</h1>
<p>① 打开超星云盘网址:http://pan-yz.chaoxing.com/ 进行登入</p>
<p>② 随便上传一个图片 -> 双击预览 -> 右键 -> 在新标签页中打开 -> 复制新标签页中的图片网址</p>
<figure data-type="image" tabindex="1"><a href="https://p.itxe.net/images/2022/11/14/cx1.gif"><img src="https://p.itxe.net/images/2022/11/14/cx1.gif" alt="img" loading="lazy"></a></figure>
<p>复制出来的网址:<code>https://imageproxy.chaoxing.com/0x0,q15,jpeg,soE2Z31QoUXrtu-Pp15uwU6Lyr-Jk4wc01pXMqFqLG_I/http://p.ananas.chaoxing.com/star3/origin/093846f84d5608bb6d995a8828f4eb8b.png</code></p>