关闭
当前位置:首页 - 音乐世界 - 正文

耳根,@程序员,想要根据 Python 3.4 玩爬虫该看些什么?,一夜情

admin 2019-04-21 201°c

互联网包括了迄今为止最有用的数据集,而且大部分可以免费揭露拜访。可是,这些数据难以复用。它们被嵌入在网站的结构和款式傍边,需求抽取出来才干运用。从网页中抽取数据的进程又称为网络爬虫,跟着越来越多的信息被发布到网络上,网络爬虫也变得越来越有用。

今日介绍的这一本书《用Python写网络爬虫(第2版)》是 Pyt布衣跑车hon 网络爬虫热销图书全新升级版,上一版年度热销近 4 万册,而本书针对 Python 3.x 编写,供给示例完好源码和实例网站建立源码,确保你可以在本地成功复现爬取网站环境,并确保网站的稳定性与牢靠性以及代码运转成果的可再现性。

点击封面检查更多概况

“网络爬虫何时有用”

假定我有一个鞋店,而且想要及时了解竞争对手的价格。我可以每天拜访他们的网站,与我店肆中鞋子的价格进行比照。可是,假如我店肆中的鞋类品种繁多,或是期望可以愈加频频地检查价格改变的话,就需求花费许多的时刻,乃至难以完成。再举一个比方,我看中了一双鞋,想比及它促销时再购买。我或许需求每天拜访这家鞋店的网站来检查这双鞋是否降价,或许需求等候几个月的时刻,我才干如愿盼到这双鞋促销。上述这两个重复性的手艺流程,都可以运用本书介绍的网络爬虫技能完成自动化处理。

在抱负状态下,网络爬虫并不是必需品,每个网站都应该供给 API,以结构化的格局同享它们的数据。然而在现实状况中,尽管一些网站现已供给了这种 API,可是它们一般会约束可以抓取的数据,以及拜访这些数据的频率。别的,网站开发人员或许会改变、移除或约束其后端 API。总归,咱们不能仅仅依托于 API 去拜访咱们所需的在线数据,而是应该学习一些网络爬虫技能的相关常识。

“本书依据 Python 3”

在本书中,咱们将彻底运用 Python 3 进行开发。Python 软件基金会现已宣告 Python 2 将会被逐渐筛选,而且只支撑到 2020 年;出于该原因,咱们和许多其他 Python 爱好者相同,现已将开发转移到对 Python 3 的支撑傍边,在本书中咱们将运用 3.6 版别。本书代码将兼容 Python 3.4+ 的版别。

假如你了解 Python Virtual Environments 或 Anaconda 的运用,那么你或许现已知道如安在一个新环境中创立 Python 3 了。假如你期望以大局办法装置 Python 3,那么咱们引荐你查找自己运用的操作系统的特星灵溯停刊定文档。就我而言,我会直接运用 Virtual Environment Wrapper,这样就可以很简略地对不同项目和 Python 版别运用多个不同的环境了。运用 Conda 环境或虚拟环境是最为引荐的,这样你就可以轻松改变依据项目需求的依托,而不会影响到你正在做的其他作业了。关于初学者来说,我引荐运用 Conda,因为其需求的装置作业更少一些。

“编写第一个网络爬虫”

为了抓取网站,咱们首要需求下载包括有感爱好数据的网页,该进程一般称为爬取(crawling)。爬取一个网站有许多种办法,而选用哪种办法愈加适宜,则取决于方针网站的结构。本文中,咱们首要会评论怎么安全地下载网页,然后会介绍如下 3 种爬取网站的常见办法:

到现在为止,咱们替换运用了抓取和爬取这两个术语,接下来让咱们先来界说这两种办法的类似点和不同点。

抓取与爬取的比照

依据你所重视的信息以及站点内容和结构的不同,你或许需求进行网络抓取或是网站爬取。那么它们有什么差异呢?

网络抓取一般针对特定网站,并在这些站点上获取指定信息。网络抓取用于拜访这些特定的页面,假如站点发作改变或许站点中的信息方位发作改变的话,则需求进行修正。例如,你或许想要经过网络抓取检查你喜爱的当地餐厅的每日特色菜,为了完成该意图,你需求抓取其网站中日常更新该信息的部分。

与之不同的是,网络爬取一般是以通用的办法构建的,其方针是一系列尖端域名的网站或是整个网络。爬取可以用来搜集更详细的信息,不过更常见的状况是爬取网络,从许多不同的站点或页面中获取小而通用的信息,然后盯梢链接到其他页面中。

除了爬取和抓取外,咱们还会在第 8 章中介绍网络爬虫。爬虫可以用来爬取指定的一系列网站,或是在多个站点乃至整个互联网中进行更广泛的爬取。

一般来说,咱们会运用特定的术语反映咱们的用例。在你开发网络爬虫时,或许会注意到它们在你想要运用的技能、库和包中的差异。在这些状况下,你对不同术语的了解,可以协助你依据所运用的术语挑选恰当的包或技能(例如,是否只用于抓取?是否也适用于爬虫?)。

下载网页

要想抓取网页,咱们首要需求将其下载下来。下面的示例脚本运用 Python 的 urllib 模块下载 URL。

1importurllib.request

2defdownload(url):

3returnurllib.request.urlopen(url).read()

当传入 URL 参数时,该函数将会下载网页并回来其 HTML。不过,这个代码片段存在一个问题,即当下载网页时,咱们或许会遇到一些无法操控的过错,比方恳求的页面或许不存在。此刻,urllib 会抛出反常,然后退出脚本。安全起见,下面再给出一个更稳建的版别,可以捕获这些反常。

1importurllib.request

2fromurllib.error importURLError, HTTPError, ContentTooShortError

3

4defdownload(url):

5print('Downloading:', url)

6try:

7html = urllib.request.urlopen(url).read()

8except(URLError, HTTPError, ContentTooShortError) ase:

9print('Download error:', e.reason)

10html = None

11returnhtml

现在,当呈现下载或 URL 过错时,该函数可以捕获到反常,然后回来 None。

① 重试下载

下载时遇到的过错经常是临时性的,比方效劳器过载时回来的 503 Service Unavailable 过错。关于此类过错,咱们可以在时间短等候后测验从头下载,因为这个效劳器问题现在或许现已处理。不过,咱们不需求对一切过错都测验从头下性感早餐妹载。假如效劳器回来的是 404 Not Found 这种过错,则阐明该网页现在并不存在,再次测验相同的恳求一般也不会呈现不同的成果。

互联网工程使命组(Internet Eng耳根,@程序员,想要依据 Python 3.4 玩爬虫该看些什么?,一夜情ineering Task Force)界说了 HTTP 过错的完好列表,从中可以了解到 4xx 过错发作在恳求存在问题时,而 5xx 过错则发作在效劳端存在问题时。所以,咱们只需求确保 download 函数在发作 5xx 过错时重试下载即可。下面是支撑重试下载功用的新版别代码。

1defdownload(url, num_retries=2):

2print('Downloading:', url)

3try:

4html = urllib.request.urlopen(url).read()

5except(URLError, HTTPError, ContentTooShortError) ase:

6print('Download error:', e.reason)

7html = None

8ifnum_retries > 0:

9ifhasattr(e, 'code') and500<= e.code < 600:

10# recursively retry 5xx HTTP errors

11returndownload(url, num_retries - 1)

12returnhtml

现在,当 download 函数遇到 5xx 过错码时,将会递归调用函数本身进行重试。此外,该函数还增加了一个参数,用于设定重试下载的次数,其默许值为两次。咱们在这里约束网页下载的测验次数,是因为效劳器过错或许暂时还没有康复。想要测验该函数,可以测验下载 http://httpstat.us/500,该网址会一向回来 500 过错码。

1>>> download('http://httpstat.us/500')

2Downloading: http://httpstat.us/500

3Download error: Internal Server Error

4Downloading: h歌媞ttp://httpstat.us/500

5Download error: Internal Server Error

6Downloading: http://httpstat.us/500

7Download error: Internal Server Error

从上面的回来成果可以看出,download 函数的行为和预期共同,先测验下载网页,在接收到 500 过错后,又进行了两次重试才抛弃。

② 设置用户署理

默许状况下,urllib 运用 Python-urllib/``3.x 作为用户署理下载网页内容,其间 3.x 是环境当时所用 Python 的版别号。假如能运用可辨识的用户署理则更好,这样可以防止咱们的网络爬虫碰到一些问题。此外,或许是因为曾经历过质量欠安的 Python 网络爬虫形成的效劳器过载,一些网站还会封禁这个默许的用户署理。

因而,为了使下载网站愈加牢靠,咱们需求操控用户署理的设定。下面的代码对 download 函数进行了修正,设定了一个默许的用户署理 ‘wswp’(即 Web Scraping with Python 的首字母缩写)。

1defdownload(url, user_agent='wswp', num_retries=2):

2print('Downloading:', url)

3request = urllib.request.Request(url)

4request.add_header('User-agent', user_agent)

5try:

6html = urllib.request.urlopen(request).read()

7except(URLError, HTTPError, ContentTooShortError) ase:

8print('Download error:', e.reason)

9html = None

10ifnum_retries > 0:

11ifhasattr(e, 'code') and500<= e.code < 600:

12# recursively retry 5xx HTTP errors

13returndownload(url, num_retries - 1)

14returnhtml

现在,假如你再次测验拜访 meetup.com,就可以看到一个合法的 HTML 了。咱们的下载函数可以在后续代码中得到复用,该函数可以捕获反常、在或许的状况下重试网站以及设置用户署理。

网站地图爬虫

在第一个简略的爬虫中,咱们将运用示例网站 robots.txt 文件中发现的网站地图来下载一切网页。为了解析网站地图,咱们将会运用一个简略的正则表达式,从 <loc> 标签中提取出 URL。

咱们需求更新代码以处理编码转化,因为咱们现在的 download 函数仅仅简略地回来了字节。下面是该示例爬虫的代码。

1importre

2

3defdownload(url, user_agent='wswp', num_retries=2, charset='utf-8'):

4print('Downloading:', url)

5request = urllib.request.Request(url)

6request.add_header('User-agent', user_agent)

7try:

8resp = urllib.request.urlopen(request)

9cs = resp.headers.get_content_charset()

10ifnotcs:

11cs = charset

12html = resp.read().decode(cs)

13except(URLError, HTTPError, ContentTooShortError) ase:

14print('Download error:', e.reason)

15html = None

16ifnum_retries > 0:

17ifhasattr(e, 'code') and500<= e.code < 600:

18# recursively retry 5xx HTTP errors

19returndownload(url, num_retries - 1)

20returnhtml

21

22defcrawl_sitemap(url):

23# download the sitemap file

24sitemap = download(url)

25# extract the sitemap links

26links = re.fi丝瓜ndall('<loc>(.*?)</loc>', sitemap)

27# download each link

28forlink inlinks:

29html = download(link)

30# 耳根,@程序员,想要依据 Python 3.4 玩爬虫该看些什么?,一夜情scrape html here

31# ...

现在,运转网站地图爬虫,从示例网站中下载一切国家或区域页面。

1>>> crawl_sitemap('http://example.python-scraping.com/sitemap.xml')

2Downloading: http://example.python-scraping.com/sitemap.xml

3Downloading: http://example.python-scraping.com/view/Afghanistan-1

4Downloading: http://example.python-scraping.com/view/Aland-Islands-2

5Downloading: http://example.python-scraping.com/view/Albania-3

6...

正如上面代码中的 download 办法所示,咱们有必要更新字符编码才干运用正则表达式处理网站呼应。Python 的 read 办法回来字节,而正则表达式期望的则是字符串。咱们的代码依托于网站维护者在呼应头中包括恰当的字符编码。假如没有回来字符编码头部,咱们将会把它设置为默许值 UTF-8,并抱有最大的期望。当然,假如回来头中的编码不正确,或是编码没有设置而且也不是 UTF-8 的话,则会抛出过错。还有一些更杂乱的办法用于猜想编码(拜见 https://pypi.python.org/pypi/chardet),该办法十分简略完成。

到现在为止,网站地图爬虫现已契合预期。不过正如前文所述,咱们无法依托 Sitemap 文件供给每个网页的链接。下一节中,咱们将会介绍另一个简略的爬虫,该爬虫不再依托于 Sitemap 文件。

ID 遍历爬虫

本节中,咱们将运用网站结构的缺陷,愈加轻松地拜访一切内容。下面是一些示例国家(或区域)的 URL。

可以看出,这些 URL 只在 URL 途径的终究一部分有所差异,包括国家(或区域)名(作为页面别号)和 ID。在 URL 中包括页面别号是十分遍及的做法,可以对查找引擎优化起到协助效果。一般状况下,Web 效劳器会疏忽这个字符串,只运用 ID 来匹配数据库中的相关记载。下面咱们将其移除,检查 http://example.python-scraping.com/view/1,测验示例网站中的链接是否仍然可用。测验成果如下图所示。

从上图中可以看出,网页仍然可以加载成功,也就是说该办法是有用的。现在,咱们就可以疏忽页面别号,只运用数据库 ID 来下载一切国家(或区域)的页面了。下面是运用了该技巧的代码片段。

1importitertools

2

3defcrawl_site(url):

4forpage initertools.count(1):

5pg_url = '{}{}'.format(url, page)

6html = dow九天神主nload(pg_url)

7ifhtml isNone:

8break

9# success - can scrape the result

现在,咱们可以运用该函数传入根底 URL。

1>>> crawl_site('http://example.python-scraping.com/view/-')

2Downloading: http://example.python-scraping.com/view/-1

3Downloading: http://example.python-scraping.com/view/-2

4Downloading: http://example.python-scraping.com/view/-3

5Downloading: http://example.python-scraping.com/view/-4

6[...]

在这段代码中,咱们对 ID 进行遍历,直到呈现下载过错时中止,咱们假定此刻抓取已抵达终究一个国家(或区域)的页面。不过,这种完成办法存在一个缺陷,那就是某些记载或许已被删去,数据库 ID 之间并不是接连的。此刻,只需拜访到某个距离点,爬虫就会当即退出。下面是这段代码的改善版别,在该版别中接连发作屡次下载过错后才会退出程序。

1defcrawl_site(url, max_errors=5):

2forpage initertools.count(1):

3pg_url = '{}{}'.format(url, page)

4html = download(pg_url)

5ifhtml isNone:

6num_errors += 1

7ifnum_errors == max_errors:

8# max errors reached, exit loop

9break

10else:

11num_errors = 0

12# success - can scrape the result

上面代码中完成的爬虫需求接连 5 次下载过错才会中止遍历,这样就很大程度上降低了遇到记载被删去或躲藏时过早中止遍历的危险。

在爬取网站时,遍历 ID 是一个很快捷的办法,可是和网站地图爬虫相同,这种办法也无法确保一向可用。比方,一些网站会检查页面别号是否在 URL 中,假如不是,则会回来 404 Not Found 过错。而另一些网站则会运用非接连大数作为 ID,或是不运用数值作为 ID,此刻遍历就难以发挥其效果了。例如,Amazon 运用 ISBN 作为可用图书的 ID,这种编码包括至少 10 位数字。运用 ID 对 ISBN 进行遍历需求测验数十亿次或许的组合,因而这种方耳根,@程序员,想要依据 Python 3.4 玩爬虫该看些什么?,一夜情法必定不是抓取该站内容最高效的办法。

正如你一向重视的那样,你或许现已注意到一些 TOO MANY REQUESTS 下载过错信息。现在无须忧虑它,咱们将会在 1.5 节的“高档功用”部分中介绍更多处理该类型过错的办法。

链接爬虫

到现在为止,咱们现已运用示原则例网站的结构特色完成了两个简略爬虫,用于下载一切已发布的国家(或区域)页面。只需这两种技能可用,就应当运用它们进行爬取,因为这两种办法将需求下载的网页数量降至最低。不过,关于另一些网站,咱们需求让爬虫体现得更像普通用户,盯梢链接,拜访感爱好的内容。

经过盯梢每个链接的办法,咱们可以很简略地下载整个网站的页面。可是,这种办法或许会下载许多并不需求的网页。例如,咱们想要从一个在线论坛中抓取用户账号概况页,那么此刻咱们只需求下载账号页,而不需求下载评论贴的页面。本文运用的链接爬虫将运用正则表达式来确认应当下载哪些页面。下面是这段代码的初始版别。

1importr耳根,@程序员,想要依据 Python 3.4 玩爬虫该看些什么?,一夜情e

2

3deflink_crawler(start_url, link_regex):

4""" Crawl from the given start URL following links matched by

5link_regex

6"""

7crawl_queue = [start_url]

8whilecrawl_queue:

9url = crawl_queue.pop()

10html = download(url)

11ifhtml isnotNone:

12continue

13# filter for links matching our regular expression

14forlink inget_links(html):

15ifre.match(link_regex, link):

16crawl_queue.append(link)

17

18defget_links(html):

19""" Return a list of links from html

20"""

21# a regular expression to extract all links from the webpage

22webpage_regex = re.compile("""<a[^>]+href=["'](.*?)["']""",

23re.IGNORECASE)

24# list of all links from the webpage

25returnwebpage_regex.findall(html)

要运转这段代码,只需求调用 link_crawler 函数,并传入两个参数:要爬取的网站 URL 以及用于匹配你想盯梢的链接的正则表达式。关于示例网站来说,咱们想要爬取的是国家(或区域)列表索引页和国家(或区域)页面。

咱们检查站点可以得知索引页链接遵从如下格局:

国家(或区域)页遵从如下格局:

因而,咱们可以用 /(index|view)/ 这个简略的正则表达式来匹配这两类网页。当爬虫运用这些输入参数运转时会发作什么呢?你会得到如下所示的下载过错。

1>>> link_crawler('http://example.python-scraping.com', '/(index|view)/')

2Downloading: http://example.python-scraping.com

3Downloading: /index/1

4Traceback (most recent call last):

5...

6ValueError: unknown url type: /index/1

可以看出,问题出在下载 /index/1 时,该链接只要网页的途径部分,而没有协议和效劳器部分,也就是说这是一个相对链接。因为阅读器知道你正在阅读哪个网页,而且可以采纳必要的过程处理这些链接,因而在阅读器阅读时,相对链接是可以正常作业的。可是,urllib 并没有上下文。为了让 urllib 可以定位网页,咱们需求将链接转化为肯定链接的办法,以便包括定位网页的一切细节。如你所愿,Python 的 urllib 中有一个模块可以用来完成该功用,该模块名为 parse。下面我是大明星现场大骂是 link_crawler 的改善版别,运用了 urljoin 办法来创立肯定途径。

1fromurllib.parse importurljoin

2

3deflink_crawler(start_url, link_regex):

4""" Crawl from 少the given start URL following links matched by

5link_regex

6"""

7crawl_queue = [start_url]

8whilecrawl_queue:

9url = crawl_queue.pop()

10html = download(url)

11ifnothtml:

12continue

13forlink inget_links(html):

14ifre.match僵尸男孩(link_regex, link):

15abs_link = urljoin(start_url, link)

16crawl_queue.append(abs_link)

当你运转这段代码时,会看到尽管下载了匹配的网页,可是相同的地址总是会被不断下载到。发生该行为的原因是这些地址相互之间存在链接。比方,澳大利亚链接到了南极洲,而南极洲又链接回了澳大利亚,此刻爬虫就会持续将这些 URL 放入行列,永久不会抵达行列尾部。要想防止重复爬取相同的链接,咱们需求记载哪些链接现已被爬取过。下面是修正后的 link_crawler 函数,具有了存储已发现 URL 的功用,可以防止重复下载。

1def link_crawler(start_url, link_regex):

2crawl_queue = [start_url]

3# keep track which URL's have seen before

4seen = set(crawl_queu耳根,@程序员,想要依据 Python 3.4 玩爬虫该看些什么?,一夜情e)

5whilecrawl_queue:

6url蔚= crawl_queue.pop()

7html = download(url)

8ifnothtml:

9continue

10forlinkinget_links(html):

11# check if link matches expected regex

12ifre.match(link_regex, link):

13abs_link = urljoin(start_url, link)

1耳根,@程序员,想要依据 Python 3.4 玩爬虫该看些什么?,一夜情4# check if have already seen this link

15ifabs_link notinseen:

16seen.add(abs_link)

17crawl_queue.append(abs_link)

当运转该脚本时,它会爬取一切地址,而且可以按期中止。终究,咱们得到了一个可用的链接爬虫!

本文摘自《用Python写网络爬虫(第2版)》

《用Python写网络爬虫》

史上首本 Python 网络爬虫图书全新升级版

针对 Python 3.x 编写

链家地产

供给示例完好源码和实例网站建立源码

本书目录

(滑动手机检查)

第1章 网络爬虫简介 1

1.1 网络爬虫何时有用 1

1.2 网络爬虫是否合法 2

1.3 Python 3 3

1.4 布景调研 4

1.4.1 检查robots.txt 4

1.4.2 检查网站地图 5

1.4.3 预算网站巨细 6

1.4.4 辨认网站所用技能 7

1.4.5 寻觅网站一切者 9

1.5 编写第一个网络爬虫 11

1.5.1 抓取与爬取的比照 11

1.5.2 下载网页 12

1.5.3 网站地图爬虫 15

1.5.4 ID遍历爬虫 17

1.5.5 链接爬虫 19

1.5.6 运用requests库 28 海尔热水器

1.6 本章小结 30

第2章 数据抓取 31

2.1 剖析网页 32

2.2 3种网页抓取办法 34

2.2.1 正则表达式 35

2.2.2 Beautiful Soup 37

2.2.3 Lxml 39

2.3 CSS挑选器和阅读器操控台 41

2.4 XPath挑选器 43

2.5 LXML和宗族树 46

2.6 功用比照 47

2.7 抓取成果 49

2.7.1 抓取总结 50

2.7.2 为链接爬虫增加抓宿世的期盼春暖花开取回调 51 完美世界寻宝天行

2.8 本章小结 55

第3章 下载缓存 56

3.1 何时运用缓存 57

3.2 为链接爬虫增加缓存支撑 57

3.3 磁盘缓存 60

3.3.1 完成磁盘缓存 62

3.3.2 缓存测验 64

3.3.3 节约磁盘空间 65

3.3.4 整理过期数据 66

3.3.5 磁盘缓存缺陷 68

3.4 键值对存储缓存 69

3.4.1 键值对存储是什么 69

3.4.2 装置Redis 70

3.4.3 Redis概述 71

3.4.4 Redis缓存完成 72

3.4.5 紧缩 74

3.4.6 测验缓存 75

3.4.7 探究requests-cache 76

3.5 本章耳根,@程序员,想要依据 Python 3.4 玩爬虫该看些什么?,一夜情小结 78

第4章 并发下载 79

4.1 100万个网页 79

4.2 串行爬虫 82

4.3 多线程爬虫 83

4.4 线程和进程怎么作业 83

4.4.1 完成多线程爬虫 84

4.4.2 多进程爬虫 87

4.5 功用 91

4.6 本章小结 94

第5章 动态内容 95

5.1 动态网页示例黄维德 95

5.2 对动态网页进行逆向工程 98

5.3 烘托动态网页 104

5.3.1 PyQt仍是PySide 105

5.3.2 履行Java 106

5.3.3 运用WebKit与网站交互 108

5.4 烘托类 111

5.5 本章小结 117

第6章 表单交互 119

6.1 登录表单 120

6.2 支撑内容更新的登录脚本扩展 128

6.3 运用Selenium完成自动化表单处理 132

6.4 本章小结 135

第7章 验证码处理 136

7.1 注册账号叉车 137

7.2 光学字符辨认 140

7.3 处理杂乱验证码 144

7.4 运用验证码处理效劳 144

7.4.1 9kw入门 145

7.4.2 陈述过错 150

7.4.3 与注册功用集成 151

7.5 验证码与机器学习 153

7.6 本章小结 153

第8章 Scrapy 154

8.1 装置Scrapy 154

8.2 发动项目 155

8.2.1 界说模型 156

8.2.2 创立爬虫 157

8.3 不同的爬虫类型 162

8.4 运用shell指令抓取 163

8.4.1 检查成果 165

8.4.2 中止与康复爬虫 167

8.5 运用Portia编写可视化爬虫 170牛肉面的做法

8.5.1 装置 170

8.5.2 标示 172

8.5.3 运转爬虫 176

8.5.4 检查成果 176

8.6 运用Scrapely完成自动化抓取 177

8.7 本章小结 178

第9章 归纳使用 179

9.1 Google查找引擎 179

9.2 Facebook 184

9.2.1 网站 184

9.2.2 Facebook API 186

9.3 Gap 188

9.4 宝马 192

9.5 本章小结 196

补白:购买后需求查询订单的话,请在微信里翻开https://j.youzan.com/XRRoH9有赞商城地址或许微信小程序里查找“码书商城”到“我的”里检查即可哦~

声明:该文观念仅代表作者自己,搜狐号系信息发布渠道,搜狐仅供给信息存储空间效劳。
admin 14文章 0评论 主页

相关文章

  用户登录