注意:
1.为了避免一个页面被采集两次, 链接去重是非常重要的。
2.写代码之前拟个大纲或画个流程图是很好的编程习惯,这么做不仅可以为你后期处理节省
很多时间,更重要的是可以防止自己在爬虫变得越来越复杂时乱了分寸。
3.处理网页重定向
服务器端重定向,网页在加载之前先改变了 URL;
客户端重定向,有时你会在网页上看到“10 秒钟后页面自动跳转到……”之类的消息,
表示在跳转到新 URL 之前网页需要加载内容。
服务器端重定向,你通常不用担心。如果你在用 Python 3.x 版本的 urllib 库,它会自
动处理重定向。不过要注意,有时候你要采集的页面的 URL 可能并不是你当前所在页
面的 URL。
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
import datetime
import random
pages = set()
#随机数种子
random.seed(datetime.datetime.now())
#获取页面所有内链的列表
def getInternalLinks(bsObj, includeurl):
internalLinks = []
#匹配以/开头的字符串,或匹配包括includeurl的字符串,+表示字符串拼接。
for link in bsObj.find_all("a", href=re.compile("^(/|.*" +includeurl+")")):
#for link in bsObj.find_all("a", href=re.compile("^(.*" + includeurl + ")")):
if link.attrs['href'] is not None:
if link.attrs['href'] not in internalLinks:
internalLinks.append(link.attrs['href'])
return internalLinks
#获取页面内所有外链的列表
def getExternalLinks(bsObj, excludeurl):
externalLinks = []
# 找出所有以"http"或"www"开头且不包含当前URL的链接
for link in bsObj.find_all("a", href=re.compile("^(http|www)((?!" +excludeurl+").)*$")):
if link.attrs['href'] is not None:
if link.attrs['href'] not in externalLinks:
externalLinks.append(link.attrs['href'])
return externalLinks
#URL链接切片,为了获得域名
def splitAddress(adress):
adressParts = adress.replace("http://", "").split("/")
return adressParts
#于外链列表中随机选取一条外链
def getRandomExternalLink(startingpage):
html = urlopen(startingpage)
bsObj = BeautifulSoup(html, "lxml")
externalLinks = getExternalLinks(bsObj, startingpage)
if len(externalLinks) == 0:
internalLinks = getInternalLinks(startingpage)
return getExternalLinks(internalLinks[random.randint(0, len(internalLinks)-1)])
else:
return externalLinks[random.randint(0, len(externalLinks)-1)]
#外链跳转,从一条外链跳转到另一条
def followExternalOnly(siteurl):
externalLink = getRandomExternalLink(siteurl)
print("随机外链:", externalLink)
followExternalOnly(externalLink)
#收集网站内所有外链列表
allExtLinks = set()
allIntLinks = set()
def getAllExternalLinks(siteurl):
html = urlopen(siteurl)
bsObj = BeautifulSoup(html, "lxml")
internalLinks = getInternalLinks(bsObj, splitAddress(siteurl)[0])
externalLinks = getExternalLinks(bsObj, splitAddress(siteurl)[0])
for link in externalLinks:
if link not in allExtLinks:
allExtLinks.add(link)
print(link)
for link in internalLinks:
if link not in allIntLinks:
allIntLinks.add(link)
print("即将获取链接的URL是:"+link)
getAllExternalLinks(link)
#从互联网采集,从一个外链跳转到另一个外链
#followExternalOnly("http://oreilly.com")
#获取网站所有外链
getAllExternalLinks("http://oreilly.com")
说明:以上代码可以执行两个功能。仅运行followExternalOnly("http://oreilly.com")是从互联网采集,从一个外链跳转到另一个外链。
仅运行getAllExternalLinks("http://oreilly.com")可以获取网站所有外链。
说一下运行getAllExternalLinks("http://oreilly.com")遇到的问题,发生raise ValueError("unknown url type: %r" % self.full_url),ValueError: unknown url type: '/oscon/oscon-or/schedule'
是不是问题出现在获取页面所有内链函数getInternalLinks()的for link in bsObj.find_all("a", href=re.compile("^(/|.*" +includeurl+")")):的部分。为何要匹配以/开头的URL?
不懂,留待以后解决吧。。。希望我别忘了。。。
备注:
BeautifulSoup的find()
和find_all()
BeautifulSoup
里的find()
和find_all()
可能是你最常用的两个函数。借助它们,你可以通过标签的不同属性轻松地过滤HTML
页面,查找需要的标签组或单个标签。
BeautifulSoup文档地址:http://beautifulsoup.readthedocs.io
find()
函数语法:
find( name , attrs , recursive , string , **kwargs )
find_all()
函数语法:
find_all( name , attrs , recursive , string , **kwargs )
搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。
name
参数可以查找所有名字为name
的tag,字符串对象会被自动忽略掉。搜索 name
参数的值可以使任一类型的过滤器,字符串,正则表达式,列表,方法等。
attrs
参数定义一个字典参数来搜索包含特殊属性的tag
。
通过string
参数可以搜搜文档中的字符串内容,与name
参数的可选值一样。
keyword
参数:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag
的属性来搜索。
find_all()
方法返回全部的搜索结构,如果文档树很大那么搜索会很慢。如果我们不需要全部结果,可以使用 limit
参数限制返回结果的数量.效果与SQL
中的limit
关键字类似,当搜索到的结果数量达到limit
的限制时,就停止搜索返回结果。
find 等价于 find_all 的 limit 等于 1 ;
调用tag
的 find_all()
方法时,Beautiful Soup
会检索当前tag
的所有子孙节点,如果只想搜索tag
的直接子节点,可以使用参数 recursive=False
。
其他BeautifulSoup对象
NavigableString
对象:表示标签里面的文字;Comment
对象:用来查找HTML
文档的注释标签。
优美胜于丑陋(Python 以编写优美的代码为目标)//
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)//
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)//
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)//
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)//
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)//
可读性很重要(优美的代码是可读的)//
即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)//
不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)//
当存在多种可能,不要尝试去猜测‘而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)。虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )//
做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量)//
如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)//
命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)//