欢迎食用『主界面』~,这里是赵苦瓜的看板娘desu~

#
【点滴记录】爬虫基础(urllib库、正则表达式)
首页 > 点滴记录    作者:赵苦瓜   2021年4月18日 9:31 星期日   热度:1711°   百度已收录  
时间:2021-4-18 9:31   热度:1711° 

最近要学爬虫(web crawler)啦~这篇文章来记录我学习的知识点。

B~1BIQPXD{BE2_]AM9`~4%0.png


另外,我做了一个测试请求的接口方便我自己的练习,支持显示GET请求和POST请求,返回Json数据。
API地址:https://oneteam.jixiaob.cn/urltest   
GET请求示例:https://oneteam.jixiaob.cn/urltest?sample=喵&num=1
返回示例:
{"method": "GET", "sample": "喵", "num": "1"}
POST请求示例:
先直接访问oneteam.jixiaob.cn/urltest,然后按F12,右键代码,选择Edit as HTML,把下面的东西复制到合适的地方,然后点击框框以外的空白处,使代码生效。然后在页面输入想要测试的内容,点击Submit提交测试。
<div>
<form method="post" action="https://oneteam.jixiaob.cn/urltest">
<input name="test"><button>Submit</button></form>
</div>
返回示例:
{"method": "POST", "test": "好家伙"}
特别注意,使用POST请求时一定要使用https,否则会301跳转变为GET请求!
当前仅支持GET请求或POST请求,如果使用其它方式会返回Not Allowed
错误返回:
{"msg": "not allowed"}

上面那个是老接口,已经关掉了,我搞了个新接口,可以显示更多信息

API地址:https://oneteam.jixiaob.cn/urltest

支持GET、POST请求。

请求示例:https://oneteam.jixiaob.cn/urltest?name=zkg&age=16

返回示例:

Message:success

META:
{'QUERY_STRING': 'name=zkg&age=16', 'REQUEST_METHOD': 'GET', 'CONTENT_TYPE': '', 'CONTENT_LENGTH': '', 'REQUEST_URI': '/urltest?name=zkg&age=16', 'PATH_INFO': '/urltest', 'DOCUMENT_ROOT': '/www/wwwroot/120.79.174.94', 'SERVER_PROTOCOL': 'HTTP/2.0', 'REQUEST_SCHEME': 'https', 'HTTPS': 'on', 'REMOTE_ADDR': '1.71.187.116', 'REMOTE_PORT': '15098', 'SERVER_PORT': '443', 'SERVER_NAME': '120.79.174.94', 'UWSGI_SCRIPT': 'djangoProject_OneTeam.wsgi', 'UWSGI_CHDIR': '/www/wwwroot/120.79.174.94/', 'HTTP_HOST': 'oneteam.jixiaob.cn', 'HTTP_SEC_CH_UA': '" Not A;Brand";v="99", "Chromium";v="90", "Microsoft Edge";v="90"', 'HTTP_SEC_CH_UA_MOBILE': '?0', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.56', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'HTTP_SEC_FETCH_SITE': 'none', 'HTTP_SEC_FETCH_MODE': 'navigate', 'HTTP_SEC_FETCH_USER': '?1', 'HTTP_SEC_FETCH_DEST': 'document', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'HTTP_COOKIE': '_ga=GA1.2.2010036960.1614158313; csrftoken=fs9FNBEqHoVp8pYV8sbYCS0RKgby7R5xnGnNc4H8wn8kX7oQCQxEJXvMb0FlDpQk', 'wsgi.input': <uwsgi._Input object at 0x7f68cbd3b0b0>, 'wsgi.file_wrapper': <built-in function uwsgi_sendfile>, 'wsgi.version': (1, 0), 'wsgi.errors': <_io.TextIOWrapper name=2 mode='w' encoding='UTF-8'>, 'wsgi.run_once': False, 'wsgi.multithread': True, 'wsgi.multiprocess': True, 'wsgi.url_scheme': 'https', 'uwsgi.version': b'2.0.19.1', 'uwsgi.core': 1, 'uwsgi.node': b'iZwz9ck5wgjfca17qoifv6Z', 'SCRIPT_NAME': ''}

Cookies:
{'_ga': 'GA1.2.2010036960.1614158313', 'csrftoken': 'fs9FNBEqHoVp8pYV8sbYCS0RKgby7R5xnGnNc4H8wn8kX7oQCQxEJXvMb0FlDpQk'}

Request_info:
{'method': 'GET', 'name': 'zkg', 'age': '16'}



正则表达式

————————————————————

正则表达式对于爬虫来说是很基础而且很重要的东西

正则表达式是用来匹配内容的,而学习这种东西,最快的方法是学与练习结合。

于是,我在学习的时候一直在用这个在线验证正则表达式的网站:http://tool.chinaz.com/regex

[]:这个括号表示一个字符。

里面可以直接放一些字符,如[abcde]就会匹配其中的一个字符

特别的,如果在这些字符前面加上^就会匹配不是这里面的字符

如[^bcdef]

也可以放范围,如

[A-Z]所有大写字母    [a-z]所有小写字母   [0-9]所有单个数字

匹配所有,[\s]   [\S](不包含非空字符,换行符)

[\w]匹配字母、数字、下划线,同 [A-Za-z0-9_]

还有一些看不见的字符,比如\f换页符 \n换行符 \r回车符 \t制表符 \v垂直制表符


有一些特殊字符,在正则中需要加上一个\才能作为字符匹配。

如^开头  $结尾  \b单词边界(空格的地方)  \B非单词边界

*匹配前面子表达式0次或多次  +匹配前面子表达式1次或多次 ?匹配前面子表达式0次或1次

.匹配除换行符\n外的任意单字符  [中括号开始  \转义  {标记限定符表达式的开始  |两项中的一个选择


限定符

{n}匹配确定的n次

{n,}匹配n+次(包含n)

{n,m}匹配n~m次(包含n,m)逗号两边不能有空格


最小匹配(非贪婪)

正则表达式默认尽可能多的匹配内容,所以如果想匹配一个小标签

^<w*?>$


以下是我自己造的用来练习的正则表达式,可能匹配的非常不准确,仅供娱乐。

1.匹配邮箱:[\w]+@[\w]+\.[\w]+

2.匹配URL:[A-Za-z]+://[\w]+\.[\w]+

3.匹配磁力链接:magnet:\?xt=urn:btih:[0-9A-z]{40}


Python中的使用方法:

提取信息


1.match()

match更适合检测某个字符串是否符合正则表达式的规则。

会尝试从字符串的起始位置匹配正则,匹配就返回成功匹配的部分,不匹配返回None

result = re.match('正则表达式', '要匹配的文本', 修饰符)

然后result.group()就是匹配的结果了。    reslut.span()是匹配出的范围

如何从匹配结果中提取一部分内容呢?将想提取的信息使用(括号)括起来即可。

这时候result.group()还是整个匹配的内容,result.group(1)就是第一个括号里面的内容,依此类推。


关于修饰符:

re.I 不区分大小写  

re.L 本地化识别匹配  

re.M 多行匹配(影响^和$) 

re.S 使.匹配包括换行符在内的所有字符

re.U 使用Unicode字符集解析字符 影响\w \W \b \B  

re.X 更灵活的格式便于正则更加易于理解


2.search()

与match不同的是,search在匹配时会扫描整个字符串,返回第一个成功匹配的结果。


3.findall()

获取匹配正则表达式的所有内容,返回一个列表。


修改信息

sub()

当然也可以用replace,但是这样太繁琐了。

比如要替换掉一段文本中的数字:

54aKS4yrsoiRS4ixSL2g

text = '54aKS4yrsoiRS4ixSL2g'
text = re.sub('\d+', '', text)
print(text)   #==》aKSyrsoiRSixSLg



创建正则表达式pattern

compile()

patten = re.compile('正则表达式')

这样以后在用的时候直接写patten就行了,不用再写一遍正则表达式了。


Urllib库

————————————————————

urllib库是Python内置的库,不需要额外安装。

他有request请求模块、error异常处理模块、parse工具模块和robotparser模块判断哪些网站可以爬。


request模块 —— Urlopen()

可以方便地实现请求的发送并得到响应。

我们先使用urlopen()的方法,来实现简单的get请求。

有时候你很高兴地写了一段代码,想获取网站的html代码:

import urllib

response = urllib.request.urlopen(‘https://www.jixiaob.cn’)
print(response.read().decode('utf-8'))


 

你很高兴的运行了它,却收获了一个报错:module 'urllib' has no attribute 'request'

urllib里面明明有request鸭,怎么没导进来呢?

我去搜了一下,他们说Python3有时候会抽风,不会将子模块自动导入进去。

有两个解决方法,一是import urllib.request as ul      二是import urllib.request

我采用了第二种方法,修改代码如下:

import urllib.request
response = urllib.request.urlopen('https://www.jixiaob.cn')
print(response.read().decode('utf-8'))


然后运行了一下,就成了,控制台里面显示了我网站主页的html源码。


我们看一下response的type,发现是<class 'http.client.HTTPResponse'>

这是个HTTPResponse类型的对象,包含read() readinto() getheader(name)  getheaders() fileno()等方法

还有msg version  status  reason  debuglevel closed 等属性


read()  返回网页的内容

getheaders()   获取响应的所有头信息

getheader(name)   获取name的响应头对应的信息

status  返回状态码


data参数

使用urlopen方法时可以加入data可选参数,这时请求方式将变为POST

传参需要使用字节流类型(bytes),可以通过字典转换得到。

比如我请求我的测试API:

import urllib.request
import urllib.parse
data_dict = {
    'word': 'hello'
}
data = bytes(urllib.parse.urlencode(data_dict), encoding='utf-8')
response = urllib.request.urlopen('https://oneteam.jixiaob.cn/urltest', data=data)
print(response.read().decode('utf-8'))


然后返回了正确的结果。

{"method": "POST", "word": "hello"}

这种请求方式是模拟表单的提交方式。


timeout参数

设置超时时间,单位秒,超时会抛异常。支持HTTP HTTPS FTP请求。

这个可以设置,如果网页长时间没有相应,就跳过抓取,可以用try实现。

我们用我的API模拟一下超时请求(我的网站反应有点快,,1秒和0.1秒都能相应,,):

我们在上面代码的基础上修改一句:

response = urllib.request.urlopen(url, data=data, timeout=0.01)

然后运行,你就会得到这个异常

urllib.error.URLError: <urlopen error timed out>

    import urllib.request
    import urllib.parse
    import socket
    import urllib.error
    data_dict = {
        'word': '好家伙'
    }
    data = bytes(urllib.parse.urlencode(data_dict), encoding='utf-8')
    try:
        response = urllib.request.urlopen('https://oneteam.jixiaob.cn/urltest', data=data, timeout=0.01)
    except urllib.error.URLError as e:
        if isinstance(e.reason, socket.timeout):
            print('Time OUT')


我们把它改成异常处理,可以发现确实是因为超时导致的错误。


context参数  ssl.SSLContext类型 指定SSL设置

cafile参数  指定CA证书

capath  指定CA证书的路径


但有时我们使用urlopen()直接请求的时候,会遇到HTTP 418错误。这是反爬虫程序返回的。

这时候就需要使用下面的request()来添加header信息,来绕过反爬虫。


request模块 —— Request()

上面的urlopen()仅支持基本的请求,不能添加header信息。如果要加入header信息,就需要用到Request请求了。

Request请求的方式是先创建一个Request类型的对象,然后在对象里面可以灵活的设置参数。

首先我们简单写一个只有url的request:

    import urllib.request
    request = urllib.request.Request('https://oneteam.jixiaob.cn/urltesst')
    response = urllib.request.urlopen(request)
    print(response.read().decode('utf-8'))


然后加入多个参数构建请求。

构建request的时候可以加入可选参数。

data 的加入方法同上面的urlopen 的方法。

headers 字典类型,可以创建的时候加入,也可以通过add_header()进行添加。

通常会通过修改UA(User-Agent)的方法来伪装浏览器,默认的UA是Python-urllib

origin_req_host 请求方的host名称或IP地址

unverifiable 请求无法验证,默认是False

method 字符串,指示请求的方法,如GET、POST、PUT等


我们在上面的基础上加入header等信息,把代码修改为下面的样子:

    import urllib.request
    import urllib.parse

    headers = {
        'Host': 'oneteam.jixiaob.cn'
    }
    data_dict = {
        'name': 'ZhaoKugua'
    }
    data = bytes(urllib.parse.urlencode(data_dict), encoding='utf-8')
    request = urllib.request.Request(url='https://oneteam.jixiaob.cn/urltest', data=data, headers=headers, method='POST')
    request.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                                     'Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46 ')
    response = urllib.request.urlopen(request)
    print(response.read().decode('utf-8'))


另外,除了这样添加header,还可以使用add_header()方法

request.add_header('User-Agent', '这里放UA信息')


高级用法:

以上的请求还没有办法处理Cookie还有代理等一些高级的内容。

urllib.request 模块里面有BaseHandler类,提供了一些基本方法。

比如:

HTTPDefalutErrorHandler:用于处理HTTP相应错误,抛出HTTPError类型的异常。

HTTPRedirectHandler:用于处理重定向

HTTPCookieProcesser:处理Cookies

ProxyHandler:设置代理,默认为空

HTTPPasswordMgr:管理密码,维护用户名和密码的表

HTTPBasicAuthHandler:管理认证

更详细的说明详见官方文档:

urllib.request — Extensible library for opening URLs — Python 3.9.5 documentation


还有一个OpenerDirector类,也叫Opener,urlopen()就算一个Opener,是人家给你封装好的比较常用的请求。

Opener使用open()的方法,和urlopen()类似。我们可以使用Handler构建Opener


1.登录验证

比如我请求了我朋友的一个网站,它是需要登录验证的:

def urllib_advanced_auth():
    from urllib.error import URLError
    from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener

    # 输入用户名和密码,还有url
    username = '这是用户名'
    password = '这是密码'
    url = '这是url'

    # 构建密码管理对象(WithDefaultRealm包含默认的远程服务器的域信息,一般没人管是None)
    p = HTTPPasswordMgrWithDefaultRealm()
    p.add_password(None, url, username, password)
    # 构建管理认证对象
    auth_handler = HTTPBasicAuthHandler(p)
    # 构建opener对象
    opener = build_opener(auth_handler)

    # 尝试登录进入
    try:
        result = opener.open(url)
        html = result.read().decode('utf-8')
        return html
    except URLError as e:
        # 错误处理,返回错误类型
        return e.reason


2.代理请求

导包ProxyHandler

# 这是个字典,协议类型: 代理链接

proxy_handler = ProxyHandler({

    'http': 'http://127.0.0.1:1080'

    'https': 'https://127.0.0.1:1080'

})

opener = build_opener(proxy_handler)

try:

    response = opener.open('https://www.google.com')

    print(response.read().decode('utf-8'))

except URLError as e:

    print(e.reason)


3.Cookies

首先可以通过cookiejar加上请求来获取cookie

def urllib_advanced_cookie():
    import http.cookiejar
    import urllib.request

    # 声明CookieJar对象
    cookie = http.cookiejar.CookieJar()
    # Cookie管理对象
    handler = urllib.request.HTTPCookieProcessor(cookie)
    # 构建opener
    opener = urllib.request.build_opener(handler)
    # 发送请求
    response = opener.open('https://www.baidu.com')
    # 打印cookie结果
    for item in cookie:
        print(item.name + " = " + item.value)
    return 0

'''
打印结果:
BAIDUID = 1D22557FD5297F0D991E8A61A2B20C4D:FG=1
BIDUPSID = 1D22557FD5297F0DF3624871F1907025
PSTM = 1620722979
BD_NOT_HTTPS = 1
'''



可以发现百度给了我们一些cookie

当然,我们也可以以文件形式存储下来

把上面声明CookieJar的语句改为:

    # 声明保存为文件的CookieJar对象
    cookie = http.cookiejar.MozillaCookieJar('Cookies.txt')  # 这里面是保存的文件名

然后请求之后来一个save

    # ignore_discard即使cookies将被丢弃也将它保存下来;ignore_expires如果cookies已经过期也将它保存并且文件已存在时将覆盖
    cookie.save(ignore_discard=True, ignore_expires=True)
运行之后我们就得到了Mozilla型浏览器的格式的Cookie

# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This is a generated file!  Do not edit.

.baidu.com	TRUE	/	FALSE	1652260281	BAIDUID	C352B4182A11A0B44F65E6350DE7AFBA:FG=1
.baidu.com	TRUE	/	FALSE	3768207928	BIDUPSID	C352B4182A11A0B4CC508C85C7932A6F
.baidu.com	TRUE	/	FALSE	3768207928	PSTM	1620724281
www.baidu.com	FALSE	/	FALSE	1620724581	BD_NOT_HTTPS	1

当然也可也选择另一种保存方式——libwww-perl(LWP)格式,声明CookieJar时改为:


cookie = http.cookiejar.LWPCookieJar('Cookies.txt')

即可。

#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="179B3D514E4AFD2EC04D591806452377:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2022-05-11 09:15:02Z"; comment=bd; version=0
Set-Cookie3: BIDUPSID=179B3D514E4AFD2E47BB0E6A6174ADBC; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2089-05-29 12:29:09Z"; version=0
Set-Cookie3: PSTM=1620724503; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2089-05-29 12:29:09Z"; version=0
Set-Cookie3: BD_NOT_HTTPS=1; path="/"; domain="www.baidu.com"; path_spec; expires="2021-05-11 09:20:02Z"; version=0


可以看到同样的Cookie,存储的方式大有不同。

加载和save的方法基本一致,使用

cookie.load('文件名', ignore_discard=True, ignore_expires=True)

即可。



4.异常处理

urllib中的error模块定义了由request模块产生的异常,出现了问题,request模块就会抛出error里面定义的异常

可以先捕获异常,然后print(e.reason),就可以使异常得到有效处理了。

1.URLError:访问了不存在的页面等。

2.HTTPError:URLError的子类,处理http请求的错误,如认证请求失败。有code(http状态码)、reason(原因)和headers(请求头)三个属性。


parse模块——解析链接


urllib中还有个parse模块,用于实现url各部分的抽取、合并以及链接转换。

1.urlparse()  实现URL的识别和分段

result = urlparse('这里填url')

def parse_url():
    from urllib.parse import urlparse
    result = urlparse('https://oneteam.jixiaob.cn/urltest?code=114514')
    print(result)

'''
输出结果:
ParseResult(scheme='https', netloc='oneteam.jixiaob.cn', path='/urltest', params='', query='code=114514', fragment='')
'''
这个结果是一个元组,可以通过下标或者名字访问。如result.scheme   result[0]


可以看到scheme(协议)、netloc(域名)、path(访问路径)、params(参数)、query(GET请求的参数)、fragment(锚点)等信息

其实urlparse第二个参数是协议,如果url里面没有指定协议的话,在这里可以指定默认协议。这个指定的协议只会在url里面没有的情况下生效。第三个参数allow_fragments,默认是True,设置为False的话fragment(锚点)会变成path或者query的一部分


2.urlunparse()   可以看作urlparse的逆过程,用于URL构造

接受的参数是可迭代对象(如list列表),长度必须为6

如data = ['http', 'jixiaob.cn', 'index.html', 'user', 'a=6', 'comment']

则urlunparse的结果就是http://jixiaob.cn/index.html;user?a=6#comment


3.urlsplit()  不解析params,合并到path中,与urlparse类似。


4.urlunsplit()  urlsplit的逆过程

参数是可迭代对象,长度必须为5


5.urljoin()  生成链接的另一种方法

urljoin('base_url', 'newurl')

分析base_url的scheme(协议)、netloc(域名)和path(路径),如果newurl有缺失的话就会拿base_url解析出来的这三个东西补充。

比如('https://www.baidu.com?keyword=miao', '?category=2')的结果就是https://www.baidu.com?category=2


6.urlencode()   GET请求的神器

可以将字典的参数转换为get请求的参数

 # get请求参数转换
    get_data_dict = {
        'name': 'zkg',
        'sex': '男'
    }
    base_url = 'https://oneteam.jixiaob.cn/urltest'
    # 这里同时也练习一下urljoin
    result2 = urljoin(base_url, 'urltest?' + urlencode(get_data_dict))
    print(result2)

'''
结果:
https://oneteam.jixiaob.cn/urltest?name=zkg&sex=%E7%94%B7
'''


可以看到请求的参数被正确转换了。


7.parse_qs()   将get的参数转换回字典

需要注意的是,传的参数是URL中?以后的get参数的内容(query),如果直接传入URL,第一个名字会出错。

所以可以搭配urlparse('URL').query使用。


8.parse_qsl()  将get的参数转换成元组,与上面的7类似。具体结构是外面是list,里面是两个长度的元组,元组里面第一个是参数名,第二个是参数值。


9.quote() 将内容转换为URL编码格式

比如quote('赵苦瓜')就是%E8%B5%B5%E8%8B%A6%E7%93%9C

这种方法可以防止中文参数乱码。


10.unquote() 将URL编码格式还原为内容

比如unquote('%E8%B5%B5%E8%8B%A6%E7%93%9C')就是赵苦瓜


有了上面的方法,就可以灵活的进行URL的解析和构造。



Robots.txt  Robots协议

————————————————————

进行爬虫时有爬虫协议要遵守,该爬的爬,不该爬的不能爬,防止对自己造成风险。

robots协议一般放在网站根目录下,如https://www.so.com/robots.txt

User-agent: 允许的搜索爬虫UA名称,至少指定一条。

Disallow:不允许抓取的目录。留空表示允许所有爬虫访问任何目录。当然允许所有也可也直接把robots.txt留空。

Allow:与Disallow连用,一般不单独使用,用来排除Disallow的限制。比如Disallow是/,表示所有目录都不允许抓取,而Allow是/public/,表示public目录可以被抓取。


爬虫名称:如Baiduspider(百度)、Googlebot(谷歌)、MSNBot(必应)、YoudaoBot(有道)、Sogou web spider(搜狗)、360Spider(360)等。


可以使用robotparser进行解析

urllib.robotparser.RobotFileParser(url='')  # 这里也可以不加参数


定义完之后有几个方法:

set_url()  传入链接

read()  读取文件并分析。进行其它操作之前必须先读取,否则所有判断都是False。

parse() 解析robots.txt文件。传入参数是robots.txt某些行的内容,会按照语法规则分析这些内容。

(read和parse选一个使用即可)

can_fetch()  第一个参数User-agent,第二个参数是要抓取的URL。根据是否可以抓取返回True或False。

mtime() 返回上次抓取和分析robots.txt的时间。(对于长时间分析和抓取很必要)

modified() 将当前时间设置为上次抓取和分析robots.txt的时间

def robot_parse():
    from urllib.robotparser import RobotFileParser

    rp = RobotFileParser()
    rp.set_url('https://www.mi.com/robots.txt')
    rp.read()
    print(rp.can_fetch('*', 'https://www.mi.com/comment/index.html'))
    print(rp.can_fetch('*', 'https://www.mi.com/mi11?product_id=1205000029&cfrom=search'))
    return 0


这里没研究明白,,用Pycarm写的都是True,用IDLE写的都是False,,就没真正判断对过,,

这,,,我,,,




本文作者:赵苦瓜      文章标题: 【点滴记录】爬虫基础(urllib库、正则表达式)
本文地址:https://blog.jixiaob.cn/?post=53
版权声明:若无注明,本文皆为“赵苦瓜のBlog~”原创,转载请保留文章出处。

返回顶部    首页    后花园  
版权所有:赵苦瓜のBlog~    站长: 赵苦瓜    程序:emlog   鲁ICP备20030743号-1   鲁公网安备37048102006726 萌ICP备20222268号    sitemap