汽車之家評論

2.7 汽車之家口碑爬蟲
7 汽車之家口碑爬蟲
需求分析
前端js反爬蟲措施分析
1問題描述
2解決方法
爬蟲框架
1 獲取所有車型的id
2本爬蟲採用scrapy框架分析所需要的評論信息爲
3將常用設置寫入sttings中
結果展示
1.需求分析
因項目需求,要爬取汽車之家的口碑數據進行下一步分析。

但是普通的爬蟲軟件(如八爪魚、火車頭、神箭手)無法爬取評論(該公司採取了反爬蟲措施)。

經分析,發現該公司的的反爬蟲措施主要是用前端js去替換顯示的字體,爲一些標籤。並且封住鼠標右鍵導致不好觀察源代碼。

本文以解決各個問題爲順序。

2.前端js反爬蟲措施分析
聲明:爬取汽車之家的主要難點在於第一步:破解前端js替換。破解方法的來源是博客園上大神Mr.Dolphin的文章反爬蟲破解系列-汽車之家利用css樣式替換文字破解方法:https://www.cnblogs.com/dyfblog/p/6753251.html 這一部分的問題大家可以移步前去獲取更精確的答案。

2.1問題描述
以任意車型(奧迪A4L)爲例:http://k.autohome.com.cn/692/

我們可以看到,表面上各個評論都由文字組成,但是打開F12開發者模式。我們就發現:一些形容詞被替換成了span標籤,如圖:

他們的具體做法是:

發佈的口碑正文中隨機抽取某幾個字使用span標籤代替,標籤內容位空,但css樣式顯示爲所代替的文。

這樣不會影響正常用戶的閱讀,只是在用鼠標選擇的時候是選不到被替換的文字的,對爬蟲則會造成採集內容不全的影響。

這些是用JS實現的,這是一段js代碼:

(function(hZ_) {

    functionEW_() { = DV_()[decodeURIComponent]('%E3%80%81%E3%80%82%E4%B8%80%E4%B8%8A%E4%B8%8B%E4%B8%8D%E4%BA%86%E4%BA%94%E5%92%8C%E5%9C%B0%E5%A4%9A%E5%A4%A7%E5%A5%BD%E5%B0%8F%E5%BE%88%E5%BE%97%E6%98%AF%E7%9A%84%E7%9D%80%E8%BF%9C%E9%95%BF%E9%AB%98%EF%BC%81%EF%BC%8C%EF%BC%9F'Ÿ yc_()); 
    = la_((yc_() 23; 3; 19; 17; 9; 1; 8; 12; 18; 13; 2; 4; 16; 5; 6; 21; 15; 11; 22; 14; 24; 0; 10; 7; 20), lf_(;)); 
    = la_((10 _7, 6 _0; 2 _33, 14 _18; 8 _45, 8 _36; 0 _71, 16 _54; 13 _76, 3 _72; 0 _107, 16 _90; 15 _110, 1 _108; 4 _139, 12 _126; 9 _152, 7 _144; 10 _169, 6 _162; 4 _193, 12 _180; 11 _204, 5 _198; 3 _230, 13 _216; 1 _250, 15 _234; 13 _256, 3 _252; 6 _281, 10 _270; 9 _296, 7 _288; 13 _310, 3 _306; 6 _335, 10 _324; 7 _352, 9 _342; 6 _371, 10 _360; 5 _390, 11 _378; 5 _408, 11 _396; 7 _424, 9 _414; 6 _443, 10 _432lf_(;)), yc_(;));
            Uj_();
            return;;
        }
    function mS_() {
        for (Gx_ = 0; Gx_ < nf_.length; Gx_++) {
            var su_ = Pn_(nf_[Gx_], ',');
            var KN_ = '';
            for (Bk_ = 0; Bk_ < su_.length; Bk_++) {
                KN_ += ui_(su_[Bk_]) + '';
            }
            Kx_(Gx_, KN_);
        }
    }
    function NH_(Gx_) {
        return '.hs_kw' + Gx_ + '_maindC';
    }
    function Ln_() {
        return '::before { content:'
    }
})(document);
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
他的邏輯是,預先定義好哪幾個字要被替換,上面代碼中的那個很多%的字符串就是被替換的文字串,然後定義好每個文字的序號,最後按照文字的序號對文字串進行重新排序並生成css樣式,注意,最一開始的span標籤的class屬性中是有個序號的,這個序號就是用來定位應該對應哪個文字。

接下來要做的就是無非就是從js代碼中找到這個文字串,找到文字串的順序,然後進行重排,然後根據span標籤序號對原文 
進行反向替換,從而得到完整的內容。

2.2解決方法:
從js代碼中找到被替換的文字串和順序

重排文字串

對原文中span標籤根據class序號進行替換

其實2、3都比較簡單,重點是第一步,找到被替換的文字串和順序,由於源代碼中js代碼是被混淆過的,無法直接看出哪個

是文字串,所以首先應該對js代碼進行反混淆,這個反混淆也不是說非得完整的還原所有的js代碼,其實只要能反混淆到能

讓我們看出文字串和順序是什麼就行了。

說一下反混淆的思路,其實很簡單。就是執行起來比較麻煩而已,混淆是利用將一個簡單的變量定義成複雜的js代碼的方法

實現的,但這種混淆方式其實是有限的(這個有限指的是混淆用的工具在生成混淆代碼時肯定是人爲預先定義好了幾種模式

,人爲定義的肯定是有限的,只要你把所有的模式找出來,就可以還原了)。舉個例子

function iq_() {
        'return iq_';
        return '3';
    }
1
2
3
4
這段代碼其實你可以簡單的認爲就是變量iq()等於’3’,使用正則匹配這樣的代碼模式,然後提取關鍵字:函數名和最後一個return的值,然後將提取到的信息保存起來用於對js代碼進行全文替換。

function cz_() {
        function _c() {
            return 'cz_';
        };
        if (_c() == 'cz__') {
            return _c();
        } else {
            return '84';
        }
    }
1
2
3
4
5
6
7
8
9
10
這段代碼複雜了一些,增加了判斷,不過也簡單,利用正則匹配這樣的模式,然後提取關鍵字:函數名、第一個return的值,判斷中==後面的值,最後一個return的值,然後自己進行判斷來確定cz_()的值應該是多少,保存起來進行全文替換。

以此類推,每種模式都可以使用正則來提取關鍵字並進行全文替換來反混淆,最後我們會得到一個大概被還原的js代碼,其中的文字串和順序都清晰可見,再使用正則匹配出來就可以了。

需要注意的一點是有時候被替換的不是單個文字,而是一些詞語,這是找到的順序是”3,1;23,5”這樣的,不過這些小伎倆應該不算什麼,很好解決。

下面給出完整的代碼:

# coding:utf8
import re
import urllib
import urllib.parse
import requests


def get_char(js):
    all_var = {}
    # 判斷混淆 無參數 返回常量 函數
    if_else_no_args_return_constant_function_functions = []
    """
    function zX_() {
            function _z() {
                return '09';
            };
            if (_z() == '09,') {
                return 'zX_';
            } else {
                return _z();
            }
        }
    """
    constant_function_regex4 = re.compile("""
        function\s+\w+\(\)\s*\{\s*
            function\s+\w+\(\)\s*\{\s*
                return\s+[\'\"][^\'\"]+[\'\"];\s*
            \};\s*
            if\s*\(\w+\(\)\s*==\s*[\'\"][^\'\"]+[\'\"]\)\s*\{\s*
                return\s*[\'\"][^\'\"]+[\'\"];\s*
            \}\s*else\s*\{\s*
                return\s*\w+\(\);\s*
            \}\s*
        \}
        """,
                                          re.X)
    l = constant_function_regex4.findall(js)
    # print("l 38",l)
    for i in l:
        function_name = re.search("""
        function\s+(\w+)\(\)\s*\{\s*
            function\s+\w+\(\)\s*\{\s*
                return\s+[\'\"]([^\'\"]+)[\'\"];\s*
            \};\s*
            if\s*\(\w+\(\)\s*==\s*[\'\"]([^\'\"]+)[\'\"]\)\s*\{\s*
                return\s*[\'\"]([^\'\"]+)[\'\"];\s*
            \}\s*else\s*\{\s*
                return\s*\w+\(\);\s*
            \}\s*
        \}
        """, i,
                                  re.X)
        if_else_no_args_return_constant_function_functions.append(function_name.groups())
        js = js.replace(i, "")
        # 替換全文
        a, b, c, d = function_name.groups()
        all_var["%s()" % a] = d if b == c else b

    # 判斷混淆 無參數 返回函數 常量
    if_else_no_args_return_function_constant_functions = []
    """
    function wu_() {
            function _w() {
                return 'wu_';
            };
            if (_w() == 'wu__') {
                return _w();
            } else {
                return '5%';
            }
        }
    """
    constant_function_regex5 = re.compile("""
        function\s+\w+\(\)\s*\{\s*
            function\s+\w+\(\)\s*\{\s*
                return\s+[\'\"][^\'\"]+[\'\"];\s*
            \};\s*
            if\s*\(\w+\(\)\s*==\s*[\'\"][^\'\"]+[\'\"]\)\s*\{\s*
                return\s*\w+\(\);\s*
            \}\s*else\s*\{\s*
                return\s*[\'\"][^\'\"]+[\'\"];\s*
            \}\s*
        \}
        """,
                                          re.X)
    l = constant_function_regex5.findall(js)
    # print("l 87",l)
    for i in l:
        function_name = re.search("""
        function\s+(\w+)\(\)\s*\{\s*
            function\s+\w+\(\)\s*\{\s*
                return\s+[\'\"]([^\'\"]+)[\'\"];\s*
            \};\s*
            if\s*\(\w+\(\)\s*==\s*[\'\"]([^\'\"]+)[\'\"]\)\s*\{\s*
                return\s*\w+\(\);\s*
            \}\s*else\s*\{\s*
                return\s*[\'\"]([^\'\"]+)[\'\"];\s*
            \}\s*
        \}
        """, i,
                                  re.X)
        if_else_no_args_return_function_constant_functions.append(function_name.groups())
        js = js.replace(i, "")
        # 替換全文
        a, b, c, d = function_name.groups()
        all_var["%s()" % a] = b if b == c else d

    # var 參數等於返回值函數
    var_args_equal_value_functions = []
    """
    var ZA_ = function(ZA__) {
            'return ZA_';
            return ZA__;
        };
    """
    constant_function_regex1 = re.compile(
        "var\s+[^=]+=\s*function\(\w+\)\{\s*[\'\"]return\s*\w+\s*[\'\"];\s*return\s+\w+;\s*\};")
    l = constant_function_regex1.findall(js)
    # print("l 119",l)
    for i in l:
        function_name = re.search("var\s+([^=]+)", i).group(1)
        var_args_equal_value_functions.append(function_name)
        js = js.replace(i, "")
        # 替換全文
        a = function_name
        js = re.sub("%s\(([^\)]+)\)" % a, r"\1", js)

    # var 無參數 返回常量 函數
    var_no_args_return_constant_functions = []
    """
    var Qh_ = function() {
            'return Qh_';
            return ';';
        };
    """
    constant_function_regex2 = re.compile("""
            var\s+[^=]+=\s*function\(\)\{\s*
                [\'\"]return\s*\w+\s*[\'\"];\s*
                return\s+[\'\"][^\'\"]+[\'\"];\s*
                \};
            """,
                                          re.X)
    l = constant_function_regex2.findall(js)
    # print("l 144",l)
    for i in l:
        function_name = re.search("""
            var\s+([^=]+)=\s*function\(\)\{\s*
                [\'\"]return\s*\w+\s*[\'\"];\s*
                return\s+[\'\"]([^\'\"]+)[\'\"];\s*
                \};
            """,
                                  i,
                                  re.X)
        var_no_args_return_constant_functions.append(function_name.groups())
        js = js.replace(i, "")
        # 替換全文
        a, b = function_name.groups()
        all_var["%s()" % a] = b

    # 無參數 返回常量 函數
    no_args_return_constant_functions = []
    """
    function ZP_() {
            'return ZP_';
            return 'E';
        }
    """
    constant_function_regex3 = re.compile("""
            function\s*\w+\(\)\s*\{\s*
                [\'\"]return\s*[^\'\"]+[\'\"];\s*
                return\s*[\'\"][^\'\"]+[\'\"];\s*
            \}\s*
        """,
                                          re.X)
    l = constant_function_regex3.findall(js)
    # print("l 176",l)
    for i in l:
        function_name = re.search("""
            function\s*(\w+)\(\)\s*\{\s*
                [\'\"]return\s*[^\'\"]+[\'\"];\s*
                return\s*[\'\"]([^\'\"]+)[\'\"];\s*
            \}\s*
        """,
                                  i,
                                  re.X)
        no_args_return_constant_functions.append(function_name.groups())
        js = js.replace(i, "")
        # 替換全文
        a, b = function_name.groups()
        all_var["%s()" % a] = b

    # 無參數 返回常量 函數 中間無混淆代碼
    no_args_return_constant_sample_functions = []
    """
    function do_() {
            return '';
        }
    """
    constant_function_regex3 = re.compile("""
            function\s*\w+\(\)\s*\{\s*
                return\s*[\'\"][^\'\"]*[\'\"];\s*
            \}\s*
        """,
                                          re.X)
    l = constant_function_regex3.findall(js)
    # print("l 206",l)
    for i in l:
        function_name = re.search("""
            function\s*(\w+)\(\)\s*\{\s*
                return\s*[\'\"]([^\'\"]*)[\'\"];\s*
            \}\s*
        """,
                                  i,
                                  re.X)
        no_args_return_constant_sample_functions.append(function_name.groups())
        js = js.replace(i, "")
        # 替換全文
        a, b = function_name.groups()
        all_var["%s()" % a] = b

    # 字符串拼接時使無參常量函數
    """
    (function() {
                'return sZ_';
                return '1'
            })()
    """
    constant_function_regex6 = re.compile("""
            \(function\(\)\s*\{\s*
                [\'\"]return[^\'\"]+[\'\"];\s*
                return\s*[\'\"][^\'\"]*[\'\"];?
            \}\)\(\)
        """,
                                          re.X)
    l = constant_function_regex6.findall(js)
    # print("l 236",l)
    for i in l:
        function_name = re.search("""
            \(function\(\)\s*\{\s*
                [\'\"]return[^\'\"]+[\'\"];\s*
                return\s*([\'\"][^\'\"]*[\'\"]);?
            \}\)\(\)
        """,
                                  i,
                                  re.X)
        js = js.replace(i, function_name.group(1))

    # 字符串拼接時使用返回參數的函數
    """
    (function(iU__) {
                'return iU_';
                return iU__;
            })('9F')
    """
    constant_function_regex6 = re.compile("""
            \(function\(\w+\)\s*\{\s*
                [\'\"]return[^\'\"]+[\'\"];\s*
                return\s*\w+;
            \}\)\([\'\"][^\'\"]*[\'\"]\)
        """,
                                          re.X)

    l = constant_function_regex6.findall(js)
    # print("l 264",l)
    for i in l:
        function_name = re.search("""
            \(function\(\w+\)\s*\{\s*
                [\'\"]return[^\'\"]+[\'\"];\s*
                return\s*\w+;
            \}\)\(([\'\"][^\'\"]*[\'\"])\)
        """,
                                  i,
                                  re.X)
        js = js.replace(i, function_name.group(1))
    print("275",js)
    # 獲取所有變量
    var_regex = "var\s+(\w+)=(.*?);\s"
    var_find = re.findall(var_regex, js)
    print("var_find",var_find)
    for var_name, var_value in var_find:
        var_value = var_value.strip("\'\"").strip()
        # print(var_name,"---",var_value)
        if "(" in var_value:
            var_value = ";"
        all_var[var_name] = var_value
    print("all var",all_var)
    # 註釋掉 此正則可能會把關鍵js語句刪除掉
    # js = re.sub(var_regex, "", js)

    for var_name, var_value in all_var.items():
        js = js.replace(var_name, var_value)
    print("----282",js)
    js = re.sub("[\s+']", "", js)
    print("----284",js)
    string_m = re.search("(%\w\w(?:%\w\w)+)", js)
    # string = urllib.parse.unquote(string_m.group(1)).encode("utf-8").decode("utf8")
    print("string_m",string_m.groups())
    string = urllib.parse.unquote(string_m.group(1)).encode("utf-8").decode("utf8")
    print(string)
    index_m = re.search("([\d,]+(;[\d,]+)+)", js[string_m.end():])
    print(index_m.group())
    string_list = list(string)
    print("str",len(string_list))
    # print("string_list",string_list)
    index_list = index_m.group(1).split(";")
    # print("index_list",index_list)
    _word_list = []
    # print(type(_word_list))
    # print(_word_list)
    i = 1
    exflag = 0;
    # deal exception

    # print("--max ",type(int(max(index_list))))
    max_index=0;
    for word_index_list in index_list:
        _word = ""
        if "," in word_index_list:
            word_index_list = word_index_list.split(",")
            word_index_list = [int(x) for x in word_index_list]
        else:
            word_index_list = [int(word_index_list)]
        for word_index in word_index_list:
            # print(word_index)
            if(word_index>max_index):
                max_index=word_index
            try:
                string_list[word_index]
            except Exception as e:
                exflag=1;
    print(max_index)
    print("exflag",exflag)
    less = max_index - len(string_list)
    print(less)
    for word_index_list in index_list:
        _word = ""
        if "," in word_index_list:
            word_index_list = word_index_list.split(",")
            # print("word_index_list",word_index_list)
            word_index_list = [int(x) for x in word_index_list]
            # print("word_index_list", word_index_list)
        else:
            word_index_list = [int(word_index_list)]
        j = 1;
        for word_index in word_index_list:
            # print("for",j)
            j += 1
            # print("word_index",word_index)
            # print("string_list[word_index]",string_list[word_index])
            try:
                _word += string_list[word_index-1-less]
            except Exception as e:
                print(e)

        # print(_word)
        _word_list.append(_word)
        # print("----------")
        # print(i)
        # print(_word_list)

        i += 1

    return _word_list


def get_complete_text_autohome(text):
    #print("text0",text)
    text = text.replace(r"\u0027","'").replace(r"\u003e",">").replace(r"\u003c","<")
    #print("text1",text)
    js = re.search("<!--@HS_ZY@--><script>([\s\S]+)\(document\);</script>", text)
    #print("find : %s" % js.group())
    if not js:
        print("  if not js:")
        return text
    try:
        #print("try0")
        char_list = get_char(js.group(1))
        print("try111")

    except Exception as e:
        print(e)
        print("except222")
        return text

    def char_replace(m):
        index = int(m.group(1))
        char = char_list[index]
        return char

    text = re.sub("<span\s*class=[\'\"]hs_kw(\d+)_[^\'\"]+[\'\"]></span>", char_replace, text)
    # print(text)
    return text


# resp = requests.get("http://k.autohome.com.cn/FrontAPI/GetFeelingByEvalId?evalId=1538569")
resp = requests.get("http://k.autohome.com.cn/FrontAPI/GetFeelingByEvalId?evalId=1585634")
resp.encoding = "gbk"
text = get_complete_text_autohome(resp.text)


print(re.search("<!--@HS_BASE64@-->.*<!--@HS_ZY@-->", text).group())
print("2")
# print(re.search("<div\s*class=[\'\"]text-con[^\'\"]*?[\'\"]>([\s\S]+?)</div>", text).group(1))
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
前一個函數是核心,用於解析js

3.爬蟲框架
3.1 獲取所有車型的id
首先利用爬蟲軟件爬取了 汽車列表 裏面的所有汽車id 
,用兩層循環爬取所有頁面的評論:

# 兩層遍歷,分別遍歷車型和頁數
        for i in car_id_list:  # i代表從車型的遍歷
            for j in range(1,101): # j代表評論頁數,range(1,3)表示1到2頁
                req = scrapy.Request("http://k.autohome.com.cn/"+str(i)+"/index_"+str(j)+".html#dataList")
                reqs.append(req)
        return reqs
1
2
3
4
5
6
3.2本爬蟲採用scrapy框架,分析所需要的評論信息爲:
# 車ID
    CAR_ID = scrapy.Field()
    # 車名
    CAR_NAME = scrapy.Field()

    # 用戶ID
    USER_ID = scrapy.Field()
    # 用戶名
    USER_NAME = scrapy.Field()

    # 購買地點
    PURCHASE_PLACE = scrapy.Field()
    # 購買時間
    PURCHASE_TIME = scrapy.Field()
    # 裸車購買價
    CAR_PRICE = scrapy.Field()
    # 購車目的
    PURCHASE_PURPOSE = scrapy.Field()

    # 評分- 空間
    SCORE_SPACE = scrapy.Field()
    # 評分- 動力
    SCORE_POWER = scrapy.Field()
    # 評分- 操控
    SCORE_CONTROL = scrapy.Field()
    # 評分- 油耗
    SCORE_FUEL_CONSUMPTION = scrapy.Field()
    # 評分- 舒適性
    SCORE_COMFORT = scrapy.Field()
    # 評分- 外觀
    SCORE_EXTERIOR = scrapy.Field()
    # 評分- 內飾
    SCORE_INTERIOR = scrapy.Field()
    # 評分- 性價比
    SCORE_COST_EFFECTIVE = scrapy.Field()

    # 評論的url
    COMMENT_URL = scrapy.Field()
    # 評論的內容
    COMMENT_CONTENT = scrapy.Field()

    # 有多少人支持這條口碑
    COMMENT_SUPPORT_QUANTITY = scrapy.Field()
    # 有多少人看過這條口碑
    COMMENT_SEEN_QUANTITY = scrapy.Field()
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
將其寫進item中。

3.3將常用設置寫入sttings中
# 繞過robots.txt
ROBOTSTXT_OBEY = False

#記錄日誌
LOG_FILE = "scrapy_autohome_log.log"

# 保存文件編碼類型
FEED_EXPORT_ENCODING = 'GBK'

# 僞裝chrome
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
1
2
3
4
5
6
7
8
9
10
11
12
這樣利用我們前面學習的scrapy框架的知識,再加上破解的js。我們成功爬取了汽車之家的數據。經過試驗爬取了 將近22萬條評論。

4.結果展示
1.數據條數:

2.數據格式

3.完整代碼參見我餓github: 
https://github.com/xqtbox/AutoHomeSpider_Scrapy
--------------------- 
作者:最小森林 
來源:CSDN 
原文:https://blog.csdn.net/u012052268/article/details/72810037 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章