第 6 章 屏幕抓取和其他实用程序

本章攻略:

  • 使用谷歌地图API搜索公司地址
  • 使用谷歌地图URL搜索地理坐标
  • 搜索维基百科中的文章
  • 使用谷歌搜索股价
  • 搜索GitHub中的源代码仓库
  • 读取BBC的新闻订阅源
  • 爬取网页中的链接

6.1 简介

本章介绍一些有趣的Python脚本,可让你从网络中获取有用信息,例如公司地址、某公司的股价或通讯社网站中的最新资讯。这些Python脚本演示了不使用复杂的API时,如何以更简单的方式获取简要信息。

按照这些攻略的做法,你能编写适应复杂需求的代码,例如,找到一家公司的详细信息,包括所在地、新闻、股价等。

6.2 使用谷歌地图API搜索公司地址

你可能想搜索所在地区内一家知名公司的地址。

6.2.1 准备工作

你可以使用Python的地理编码库pygeocoder搜索本地公司。你需要使用pipeasy_install从PyPI上安装这个库,输入的命令为$ pip install pygeocoder$ easy_install pygeocoder

6.2.2 实战演练

我们来使用几行Python代码查找英国著名零售商Argos有限公司的地址。

代码清单6-1是一个简单的地理编码示例,用于搜索一家公司的地址,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 6
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. from pygeocoder import Geocoder
  6. def search_business(business_name):
  7. results = Geocoder.geocode(business_name)
  8. for result in results:
  9. print result
  10. if __name__ == '__main__':
  11. business_name = "Argos Ltd, London"
  12. print "Searching %s" %business_name
  13. search_business(business_name)

这个脚本会打印出Argos有限公司的地址,如下所示。输出的内容根据所安装的地理编码库会有细微的差别。

  1. $ python 6_1_search_business_addr.py
  2. Searching Argos Ltd, London
  3. Argos Ltd, 110-114 King Street, London, Greater London W6 0QP, UK

6.2.3 原理分析

这个攻略依赖于Python的第三方地理编码库。

这个攻略定义了一个简单的函数,search_business(),其参数是公司名,然后它把公司名传给geocode()方法。geocode()方法可能返回零个或多个结果,具体取决于搜索的关键字。

在这个攻略中,传给geocode()方法的搜索关键字是“Argos Ltd, London”。得到的结果是Argos有限公司的地址,即“110-114 King Street, London, Greater London W6 0QP, UK”。

6.2.4 参考资源

pygeocoder库很强大,有很多与地理编码有关的有趣和有用的功能。在开发者的网站中有更详细的说明,地址为https://bitbucket.org/xster/pygeocoder/wiki/Home

6.3 使用谷歌地图URL搜索地理坐标

有时你需要一个简单的函数,只通过城市名即可找出城市的地理坐标。你或许不想为这么简单的任务安装任何第三方库。

6.3.1 实战演练

在这个简单的屏幕抓取示例中,我们使用谷歌地图URL查询一个城市的纬度和经度。在谷歌地图中搜索一次之后就能找到查询所需的URL。我们可以按照下面的步骤从谷歌地图中提取信息。

城市名使用argparse模块从命令行中获取。

地图搜索URL使用urllib模块中的urlopen()函数打开。如果URL正确,会得到一个XML格式的输出。

然后处理XML输出,获取该城市的地理坐标。

代码清单6-2使用谷歌地图查找一个城市的地理坐标,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 6
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import argparse
  6. import os
  7. import urllib
  8. ERROR_STRING = '<error>'
  9. def find_lat_long(city):
  10. """ Find geographic coordinates """
  11. # Encode query string into Google maps URL
  12. url = 'http://maps.google.com/?q=' + urllib.quote(city) + '&output=js'
  13. print 'Query: %s' % (url)
  14. # Get XML location from Google maps
  15. xml = urllib.urlopen(url).read()
  16. if ERROR_STRING in xml:
  17. print '\nGoogle cannot interpret the city.'
  18. return
  19. else:
  20. # Strip lat/long coordinates from XML
  21. lat,lng = 0.0,0.0
  22. center = xml[xml.find('{center')+10:xml.find('}',xml.find('{center'))]
  23. center = center.replace('lat:','').replace('lng:','')
  24. lat,lng = center.split(',')
  25. print "Latitude/Longitude: %s/%s\n" %(lat, lng)
  26. if __name__ == '__main__':
  27. parser = argparse.ArgumentParser(description='City Geocode Search')
  28. parser.add_argument('--city', action="store", dest="city", required=True)
  29. given_args = parser.parse_args()
  30. print "Finding geographic coordinates of %s" %given_args.city
  31. find_lat_long(given_args.city)

运行这个脚本后,会看到类似下面的输出:

  1. $ python 6_2_geo_coding_by_google_maps.py --city=London
  2. Finding geograhic coordinates of London
  3. Query: http://maps.google.com/?q=London&output=js
  4. Latitude/Longitude: 51.511214000000002/-0.119824

6.3.2 原理分析

这个攻略从命令行中获取城市名,然后将其传给find_lat_long()函数。这个函数使用urllib模块中的urlopen()函数查询谷歌地图服务,得到XML格式的结果。然后,在得到的结果中搜索字符串'',如果找不到就说明结果没问题。

如果你把原始的XML打印出来,会发现有很多字符,这些字符是为浏览器生成的。在浏览器中,需要在地图上显示图层。但在这个攻略中,我们只需要纬度和经度。

我们使用字符串处理方法find()从原始的XML中提取出纬度和经度。我们搜索的关键字是“center”,以便找出地理坐标信息。但得到的结果中包含一些额外的字符,所以又调用replace()方法将其删除。

你可以使用这个攻略查找世界上任何一座城市的经纬度。

6.4 搜索维基百科中的文章

维基百科是个非常棒的网站,汇聚了几乎所有的信息,例如人物、场所和技术等。如果你想使用Python脚本在维基百科中搜索点儿什么,可以参考这个攻略。

下面是一篇文章示例:

图6-1

6.4.1 准备工作

你需要使用pipeasy_install从PyPI上安装第三方库pyyaml,输入的命令为$ pip install pyyaml$ easy_install pyyaml

6.4.2 实战演练

我们使用关键字“Islam”搜索维基百科,然后把结果打印出来,一行显示一个。

代码清单6-3展示了如何在维基百科中搜索文章,如下所示:

  1. #!usrbin/env python
  2. # -*- coding: utf-8 -*-
  3. # Python Network Programming Cookbook -- Chapter - 6
  4. # This program is optimized for Python 2.7.
  5. # It may run on any other version with/without modifications.
  6. import argparse
  7. import re
  8. import yaml
  9. import urllib
  10. import urllib2
  11. SEARCH_URL = 'http://%s.wikipedia.org/w/api.php?action=query&list=search&srsearch=%s&sroffset=%d&srlimit=%d&format=yaml'
  12. class Wikipedia:
  13. def __init__(self, lang='en'):
  14. self.lang = lang
  15. def getcontent(self, url):
  16. request = urllib2.Request(url)
  17. request.add_header('User-Agent', 'Mozilla/20.0')
  18. try:
  19. result = urllib2.urlopen(request)
  20. except urllib2.HTTPError, e:
  21. print "HTTP Error:%s" %(e.reason)
  22. except Exception, e:
  23. print "Error occured: %s" %str(e)
  24. return result
  25. def search_content(self, query, page=1, limit=10):
  26. offset = (page - 1) limit
  27. url = SEARCH_URL % (self.lang, urllib.quote_plus(query), offset, limit)
  28. content = self.getcontent(url).read()
  29. parsed = yaml.load(content)
  30. search = parsed['query']['search']
  31. if not search:
  32. return
  33. results = []
  34. for article in search:
  35. snippet = article['snippet']
  36. snippet = re.sub(r'(?m)<.?>', '', snippet)
  37. snippet = re.sub(r'\s+', ' ', snippet)
  38. snippet = snippet.replace(' . ', '. ')
  39. snippet = snippet.replace(' , ', ', ')
  40. snippet = snippet.strip()
  41. results.append({
  42. 'title' : article['title'].strip(),
  43. 'snippet' : snippet
  44. })
  45. return results
  46. if __name__ == '__main__':
  47. parser = argparse.ArgumentParser(description='Wikipedia search')
  48. parser.add_argument('--query', action="store", dest="query", required=True)
  49. given_args = parser.parse_args()
  50. wikipedia = Wikipedia()
  51. search_term = given_args.query
  52. print "Searching Wikipedia for %s" %search_term
  53. results = wikipedia.search_content(search_term)
  54. print "Listing %s search results..." %len(results)
  55. for result in results:
  56. print "==%s== \n \t%s" %(result['title'], result['snippet'])
  57. print "---- End of search results ----"

运行这个脚本在维基百科中搜索“Islam”,得到的输出结果如下所示:

  1. $ python 6_3_search_article_in_wikipedia.py --query='Islam'
  2. Searching Wikipedia for Islam
  3. Listing 10 search results...
  4. ==Islam==
  5. Islam. (ˈ|ɪ| s | l |ɑː| m مالسإلا, ar | ALA | alIslām ælʔɪsˈlæːm | IPA | ar-al_islam. ...
  6. ==Sunni Islam==
  7. Sunni Islam | s | uː | n | i or ˈ | s | ʊ | n | i |) is the
  8. largest branch of Islam ; its adherents are referred to in Arabic as ...
  9. ==Muslim==
  10. A Muslim, also spelled Moslem is an adherent of Islam, a
  11. monotheistic Abrahamic religion based on the Qur'an —which Muslims
  12. consider the ...
  13. ==Sharia==
  14. is the moral code and religious law of Islam. Sharia deals with
  15. many topics addressed by secular law, including crime, politics, and ...
  16. ==History of Islam==
  17. The history of Islam concerns the Islamic religion and its
  18. adherents, known as Muslim s. " "Muslim" is an Arabic word meaning
  19. "one who ...
  20. ==Caliphate==
  21. a successor to Islamic prophet Muhammad ) and all the Prophets
  22. of Islam. The term caliphate is often applied to successions of
  23. Muslim ...
  24. ==Islamic fundamentalism==
  25. Islamic ideology and is a group of religious ideologies seen as
  26. advocating a return to the "fundamentals" of Islam : the Quran and
  27. the Sunnah. ...
  28. ==Islamic architecture==
  29. Islamic architecture encompasses a wide range of both secular
  30. and religious styles from the foundation of Islam to the present day. ...
  31. ---- End of search results ----

6.4.3 原理分析

首先,我们组建了用于搜索文章的维基百科URL模板。然后定义一个名为Wikipedia的类,其中有两个方法:getcontent()search_content()。默认情况下,初始化时,把语言属性lang设为en(英语)。

命令行中输入的查询字符串传给search_content()方法,替换模板中的语言、查询字符串、偏移页数和返回结果数量,得到真正的搜索URL。search_content()方法的page参数是可选的,偏移页数由表达式(page -1) * limit计算得出。

搜索结果的内容通过getcontent()方法获得。在getcontent()方法中调用了urllib模块中的urlopen()函数。在搜索URL中,我们把结果的格式设为yaml,这基本上就是纯文本文件。然后再使用Python的pyyaml库解析得到的yaml格式搜索结果。

搜索结果使用正则表达式替换各结果中的内容。例如,re.sub(r'(?m)<.*?>', '', snippet)在字符串snippet中替换匹配(?m)<.*?>)模式的内容。若想进一步学习正则表达式,请阅读Python文档,地址是http://docs.python.org/2/howto/regex.html

在维基百科中,每篇文章都有一个摘要或简短说明。我们创建了一个由字典组成的列表,列表中的每个元素都包含一个搜索结果的标题和摘要。然后遍历这个由字典组成的列表,打印搜索结果。

6.5 使用谷歌搜索股价

如果你关注某公司的股价,这个攻略能帮助你了解该公司今天的股价。

6.5.1 准备工作

假设你知道所关注的公司在股票交易所挂牌上市使用的代号。如果不知道,可以在该公司的网站中查找,或者在谷歌中搜索。

6.5.2 实战演练

我们要使用谷歌财经(http://finance.google.com/)搜索指定公司的股价。你可以在命令行中输入代号,如下面的代码所示。

代码清单6-4说明如何在谷歌中搜索股价,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 6
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import argparse
  6. import urllib
  7. import re
  8. from datetime import datetime
  9. SEARCH_URL = 'http://finance.google.com/finance?q='
  10. def get_quote(symbol):
  11. content = urllib.urlopen(SEARCH_URL + symbol).read()
  12. m = re.search('id="ref_694653_l".*?>(.*?)<', content)
  13. if m:
  14. quote = m.group(1)
  15. else:
  16. quote = 'No quote available for: ' + symbol
  17. return quote
  18. if __name__ == '__main__':
  19. parser = argparse.ArgumentParser(description='Stock quote search')
  20. parser.add_argument('--symbol', action="store", dest="symbol", required=True)
  21. given_args = parser.parse_args()
  22. print "Searching stock quote for symbol '%s'" %given_args.symbol
  23. print "Stock quote for %s at %s: %s" %(given_args.symbol , datetime.today(), get_quote(given_args.symbol))

运行这个脚本后,会看到类似下面的输出。这里,我们输入代号goog,搜索谷歌的股价,如下所示:

  1. $ python 6_4_google_stock_quote.py --symbol=goog
  2. Searching stock quote for symbol 'goog'
  3. Stock quote for goog at 2013-08-20 18:50:29.483380: 868.86

6.5.3 原理分析

在这个脚本中,使用urllib模块中的urlopen()函数从谷歌财经网站中获取股票数据。

我们使用正则表达式库re,从第一组数据中获取股价。re库的search函数很强大,能搜索内容并过滤特定公司的ID。

我们使用这个攻略搜索了谷歌的股价,在2013年8月20日,其股价是868.86。

6.6 搜索GitHub中的源代码仓库

作为一个Python程序员,你可能已经知道GitHub(http://www.github.com,如下面的截图所示)这个源代码分享网站了。使用GitHub,你可以把源代码私下分享给团队,也可以公开分享给全世界。GitHub有一个好用的API接口,可以查询任何源代码仓库。这个攻略或许能为你的源代码搜索引擎提供一些起步代码。

图6-2

6.6.1 准备工作

运行这个脚本需要安装Python第三方库requests,执行命令$ pip install requests$ easy_install requests即可。

6.6.2 实战演练

我们要定义search_repository()函数,其参数是作者名(即程序员名)、仓库名和搜索的键名,得到的是键对应的结果。根据GitHub的API,可用的搜索键有:issues_urlhas_wikiforks_urlmirror_urlsubscription_urlnotifications_urlcollaborators_urlupdated_atprivatepulls_urlissue_comment_urllabels_urlfull_nameownerstatuses_urlidkeys_urldescriptiontags_urlnetwork_countdownloads_urlassignees_urlcontents_urlgit_refs_urlopen_issues_countclone_urlwatchers_countgit_tags_urlmilestones_urllanguages_urlsizehomepageforkcommits_urlissue_events_urlarchive_urlcomments_urlevents_urlcontributors_urlhtml_urlforkscompare_urlopen_issuesgit_urlsvn_urlmerges_urlhas_issuesssh_urlblobs_urlmaster_branchgit_commits_urlhooks_urlhas_downloadswatchersnamelanguageurlcreated_atpushed_atforks_countdefault_branchteams_urltrees_urlorganizationbranches_urlsubscribers_urlstargazers_url

代码清单6-5给出了在GitHub中搜索源代码仓库详情的代码,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 6
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. SEARCH_URL_BASE = 'https://api.github.com/repos'
  6. import argparse
  7. import requests
  8. import json
  9. def search_repository(author, repo, search_for='homepage'):
  10. url = "%s/%s/%s" %(SEARCH_URL_BASE, author, repo)
  11. print "Searching Repo URL: %s" %url
  12. result = requests.get(url)
  13. if(result.ok):
  14. repo_info = json.loads(result.text or result.content)
  15. print "Github repository info for: %s" %repo
  16. result = "No result found!"
  17. keys = []
  18. for key,value in repo_info.iteritems():
  19. if search_for in key:
  20. result = value
  21. return result
  22. if __name__ == '__main__':
  23. parser = argparse.ArgumentParser(description='Github search')
  24. parser.add_argument('--author', action="store", dest="author", required=True)
  25. parser.add_argument('--repo', action="store", dest="repo", required=True)
  26. parser.add_argument('--search_for', action="store", dest="search_for", required=True)
  27. given_args = parser.parse_args()
  28. result = search_repository(given_args.author, given_args.repo, given_args.search_for)
  29. if isinstance(result, dict):
  30. print "Got result for '%s'..." %(given_args.search_for)
  31. for key,value in result.iteritems():
  32. print "%s => %s" %(key,value)
  33. else:
  34. print "Got result for %s: %s" %(given_args.search_for, result)

如果运行这个脚本搜索Python Web框架Django的拥有者,得到的结果如下所示:

  1. $ python 6_5_search_code_github.py --author=django --repo=django --search_for=owner
  2. Searching Repo URL: https://api.github.com/repos/django/django
  3. Github repository info for: django
  4. Got result for 'owner'...
  5. following_url => https://api.github.com/users/django/following{/other_user}
  6. events_url => https://api.github.com/users/django/events{/privacy}
  7. organizations_url => https://api.github.com/users/django/orgs
  8. url => https://api.github.com/users/django
  9. gists_url => https://api.github.com/users/django/gists{/gist_id}
  10. html_url => https://github.com/django
  11. subscriptions_url => https://api.github.com/users/django/subscriptions
  12. avatar_url => https://1.gravatar.com/avatar/fd542381031aa84dca86628ece84f
  13. c07?d=https%3A%2F%2Fidenticons.github.com%2Fe94df919e51ae96652259468415d4f77.png
  14. repos_url => https://api.github.com/users/django/repos
  15. received_events_url => https://api.github.com/users/django/received_events
  16. gravatar_id => fd542381031aa84dca86628ece84fc07
  17. starred_url => https://api.github.com/users/django/starred{/owner}{/repo}
  18. login => django
  19. type => Organization
  20. id => 27804
  21. followers_url => https://api.github.com/users/django/followers

6.6.3 原理分析

这个脚本接收三个命令行参数:仓库作者(--author)、仓库名(--repo)、要搜索的信息(--search_for)。这些参数由argparse模块解析。

search_repository()函数中,把这些命令行参数添加到一个固定的搜索URL中,然后调用requests模块中的get()方法获取搜索结果。

默认情况下,返回的搜索结果是JSON格式。然后,调用json模块中的loads()方法处理搜索结果。在结果中查找搜索的键,把键对应的值返回给search_repository()函数的调用程序。

__main__块中,我们检查搜索结果是否为一个Python字典实例。如果是,就遍历结果,把键值对打印出来;否则,直接打印结果。

6.7 读取BBC的新闻订阅源

如果你在开发一个新闻和故事相关的社会化网站,或许想显示世界上不同新闻通讯社(例如BBC和路透社)的新闻。让我们试着使用Python脚本从BBC读取新闻。

6.7.1 准备工作

这个攻略依赖于Python第三方库feedparser。你可以执行下面的命令安装这个库:

  1. $ pip install feedparser

  1. $ easy_install feedparser

6.7.2 实战演练

首先,我们要从BBC的网站上找到新闻订阅源的URL。这个URL可以作为搜索不同类型新闻的模板,例如国际新闻、国内新闻、健康新闻、商业新闻和技术新闻。新闻的类型可以从用户的输入中获取。然后,调用read_news()函数,从BBC的网站中读取新闻。

代码清单6-6说明了如何从BBC的新闻订阅源读取新闻,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 6
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. from datetime import datetime
  6. import feedparser
  7. BBC_FEED_URL = 'http://feeds.bbci.co.uk/news/%s/rss.xml'
  8. def read_news(feed_url):
  9. try:
  10. data = feedparser.parse(feed_url)
  11. except Exception, e:
  12. print "Got error: %s" %str(e)
  13. for entry in data.entries:
  14. print(entry.title)
  15. print(entry.link)
  16. print(entry.description)
  17. print("\n")
  18. if __name__ == '__main__':
  19. print "==== Reading technology news feed from bbc.co.uk (%s)====" %datetime.today()
  20. print "Enter the type of news feed: "
  21. print "Available options are: world, uk, health, sci-tech, business, technology"
  22. type = raw_input("News feed type:")
  23. read_news(BBC_FEED_URL %type)
  24. print "==== End of BBC news feed ====="

运行这个脚本后,会显示可选的新闻类别。如果选择技术类,就会显示技术相关的最新新闻,如下面的输出所示:

  1. $ python 6_6_read_bbc_news_feed.py
  2. ==== Reading technology news feed from bbc.co.uk (2013-08-20 19:02:33.940014)====
  3. Enter the type of news feed:
  4. Available options are: world, uk, health, sci-tech, business, technology
  5. News feed type:technology
  6. Xbox One courts indie developers
  7. http://www.bbc.co.uk/news/technology-23765453#sa-ns_mchannel=rss&ns_source=PublicRSS20-sa
  8. Microsoft is to give away free Xbox One development kits to encourage
  9. independent developers to self-publish games for its forthcoming console.
  10. Fast in-flight wi-fi by early 2014
  11. http://www.bbc.co.uk/news/technology-23768536#sa-ns_mchannel=rss&ns_source=PublicRSS20-sa
  12. Passengers on planes, trains and ships may soon be able to take advantage
  13. of high-speed wi-fi connections, says Ofcom.
  14. Anonymous 'hacks council website'
  15. http://www.bbc.co.uk/news/uk-england-surrey-23772635#sa-ns_mchannel=rss&ns_source=PublicRSS20-sa
  16. A Surrey council blames hackers Anonymous after references to a Guardian
  17. journalist's partner detained at Heathrow Airport appear on its website.
  18. Amazon.com website goes offline
  19. http://www.bbc.co.uk/news/technology-23762526#sa-ns_mchannel=rss&ns_source=PublicRSS20-sa
  20. Amazon's US website goes offline for about half an hour, the latest high-profile internet firm to face such a problem in recent days.
  21. [TRUNCATED]

6.7.3 原理分析

在这个攻略中,read_news()函数依赖于Python第三方模块feedparserfeedparser模块中的parse()方法以结构化形式返回订阅源中的数据。

在这个攻略中,parse()方法解析指定的订阅源URL。这个URL由BBC_FEED_URL和用户的输入组成。

如果无异常就调用parse()方法获取订阅源中的数据,再把数据中的内容打印出来,例如每条新闻的标题、链接和描述。

6.8 爬取网页中的链接

有时你可能想在网页中查找某个关键字。在网页浏览器中,可以使用页内搜索功能找到关键字。有些浏览器还能高亮显示关键字。如果情况复杂,你或许还想进一步深入查找,跟踪网页中的每个URL,查找某个关键字。这个攻略的目的是自动化完成这样的任务。

6.8.1 实战演练

我们来定义search_links()函数,它接收三个参数:搜索的URL、递归搜索的深度、搜索关键字。因为每个URL对应的内容中都可能有很多链接,而且各链接指向的内容中或许还有更多的链接要爬取,所以我们要递归搜索。为了限制递归搜索,我们定义了一个深度。到达指定的深度后,就不会再继续搜索了。

代码清单6-7给出了爬取网页中链接的代码,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 6
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import argparse
  6. import sys
  7. import httplib
  8. import re
  9. processed = []
  10. def search_links(url, depth, search):
  11. # Process http links that are not processed yet
  12. url_is_processed = (url in processed)
  13. if (url.startswith("http://") and (not url_is_processed)):
  14. processed.append(url)
  15. url = host = url.replace("http://", "", 1)
  16. path = ""
  17. urlparts = url.split("")
  18. if (len(urlparts) > 1):
  19. host = urlparts[0]
  20. path = url.replace(host, "", 1)
  21. # Start crawing
  22. print "Crawling URL path:%s%s " %(host, path)
  23. conn = httplib.HTTPConnection(host)
  24. req = conn.request("GET", path)
  25. result = conn.getresponse()
  26. # find the links
  27. contents = result.read()
  28. all_links = re.findall('href="(.*?)"', contents)
  29. if (search in contents):
  30. print "Found " + search + " at " + url
  31. print " ==> %s: processing %s links" %(str(depth), str(len(all_links)))
  32. for href in all_links:
  33. # Find relative urls
  34. if (href.startswith("")):
  35. href = "http:/" + host + href
  36. # Recurse links
  37. if (depth > 0):
  38. search_links(href, depth-1, search)
  39. else:
  40. print "Skipping link: %s ..." %url
  41. if __name__ == '__main__':
  42. parser = argparse.ArgumentParser(description='Webpage link crawler')
  43. parser.add_argument('--url', action="store", dest="url", required=True)
  44. parser.add_argument('--query', action="store", dest="query", required=True)
  45. parser.add_argument('--depth', action="store", dest="depth", default=2)
  46. given_args = parser.parse_args()
  47. try:
  48. search_links(given_args.url, given_args.depth,given_args.query)
  49. except KeyboardInterrupt:
  50. print "Aborting search by user request."

如果运行这个脚本在www.python.org中搜索python,会看到类似下面的输出:

  1. $ python 6_7_python_link_crawler.py --url='http://python.org' --query='python'
  2. Crawling URL path:python.org/
  3. Found python at python.org
  4. ==> 2: processing 123 links
  5. Crawling URL path:www.python.org/channews.rdf
  6. Found python at www.python.org/channews.rdf
  7. ==> 1: processing 30 links
  8. Crawling URL path:www.python.org/download/releases/3.4.0/
  9. Found python at www.python.org/download/releases/3.4.0/
  10. ==> 0: processing 111 links
  11. Skipping link: https://ep2013.europython.eu/blog/2013/05/15/epc20145-call-proposals ...
  12. Crawling URL path:www.python.org/download/releases/3.2.5/
  13. Found python at www.python.org/download/releases/3.2.5/
  14. ==> 0: processing 113 links
  15. ...
  16. Skipping link: http://www.python.org/download/releases/3.2.4/ ...
  17. Crawling URL path:wiki.python.org/moin/WikiAttack2013
  18. ^CAborting search by user request.

6.8.2 原理分析

这个攻略接收三个命令行参数:搜索的URL(--url)、查询字符串(--query)、递归深度(--depth)。这些参数由argparse模块解析。

把这三个参数传入search_links()函数后,会递归遍历在指定网页中找到的所有链接。如果运行很长时间还没结束,你应该提前退出脚本。因此,我们才把search_links()函数放在try-except块中,以便捕获用户在键盘中输入的中断操作,例如按Ctrl+C键。

search_links()函数把访问过的链接保存在processed列表中。processed放在全局作用域中,以便递归调用函数时使用。

每次搜索时,都要保证只处理HTTP URL,防止出现潜在的SSL验证错误。URL被分成主机和路径两部分。顶层爬取使用httplib库中的HTTPConnection()函数实例化。然后发起一个GET请求,使用正则表达式模块re处理响应,收集响应中的所有链接。然后在响应中查找要搜索的关键字。如果找到了关键字,就打印出来。

收集到的链接使用相同的方式递归访问。如果找到了相关链接,就在地址前加上http://,转换成完整的URL。如果搜索深度大于零,说明可以递归搜索,把深度减去一后,再次调用搜索函数。当搜索深度为零时,递归结束。