- 之所以叫网络爬虫,是因为他们可以沿着网络爬行, 本质是一种递归方式: 为了找到url链接, 必须首先获取网页内容, 检查页面内容, 再寻找另一个url, 获取页面内容, 不断循环
- 使用网络爬虫的时候, 应谨慎地考虑需要消耗多少网络流量, 还要尽量思考能否让采集目标的服务器负载更低
- 维基百科六度分隔理论 - 任何2个不相干的词条, 都可以通过总数不超过6条的词条链接起来(包括原来的2个词条)
- 由此, 写爬虫时, 如何高效地通过最少的链接点击次数到达目的站点, 不仅使爬虫工作效率更高, 且对服务器的负载影响也越小
开始采集
往往先对每个要获取信息的页面找一下规律,用正则匹配一下规则,
html = urlopen("http://en.wikipedia.org/wiki/Kevin_Bacon");
bs0bj = BeautifulSoup(html,"html.parser");
for link in bs0bj.find("div",{"id":"bodyContent"}).findAll("a",{"href":re.compile("^(/wiki/)((?!:).)*$")}):
if 'href' in link.attrs:
print(link.attrs['href']);
构建一个页面到另一个页面的爬虫:
- 爬取的内容往往携带许多无用的信息, 在处理之前, 应根据实际剔除无用信息
- 随机算法都努力创造一种均匀分布且难以预测的数据序列, 但在算法初始阶段都需要提供一个随机数种子(random seed). 完全相同的种子每次将产生相同的”随机”数序列. 可以用系统当前时间作为随机数种子, 而使程序运行更具随机性.
- python的伪随机数生成器用的是梅森旋转算法(https://en.wikipedia.org/wiki/Mersenne_Twister)
程序主函数是从http://en.wikipedia.org/wiki/Kevin_Bacon
起始页面里的词条链接列表(links变量)随机选择一个,并继续作为页面爬取。
from urllib.request import urlopen
from bs4 import BeautifulSoup
import datetime
import random
import re
random.seed(datetime.datetime.now());
def getLinks(articleUrl):
html = urlopen("http://en.wikipedia.org"+articleUrl);
bs0bj = BeautifulSoup(html,"html.parser");
return bs0bj.find("div",{"id":"bodyContent"}).findAll("a",{"href":re.compile("^(/wiki/)((?!:).)*$")});
links =getLinks("/wiki/Kevin_Bacon");
while len(links) > 0:
newArticle = links[random.randint(0,len(links)-1)].attrs["href"];
print(newArticle);
links = getLinks(newArticle);
异常处理
结合第一章相关内容,完善上面代码可能出现的异常处理。
from urllib.request import urlopen
from bs4 import BeautifulSoup
from urllib.error import HTTPError,URLError
import datetime
import random
import re
random.seed(datetime.datetime.now());
def getLinks(articleUrl):
try:
html = urlopen("http://en.wikipedia.org"+articleUrl);
except (HTTPError,URLError) as e:
return None;
try:
bs0bj = BeautifulSoup(html,"html.parser");
except AttributeError as e:
return None;
return bs0bj.find("div",{"id":"bodyContent"}).findAll("a",{"href":re.compile("^(/wiki/)((?!:).)*$")});
links =getLinks("/wiki/yexiaoju");
if(links==None):
print("Links could not be found");
else:
while len(links) > 0:
newArticle = links[random.randint(0,len(links)-1)].attrs["href"];
print(newArticle);
links = getLinks(newArticle);
采集整个网络
浅网(surface web)是搜索引擎可以抓取的网络; 暗网(dark web)或深网(deep web)则是另一部分. 据不完全统计,互联网中其实约 90% 的网络都是深网.
暗网,也被称为 Darknet 或 dark Internet,完全是另一种“怪兽”。它们也建立在已有的网络基础上,但是使用 Tor 客户端,带有运行在 HTTP 之上的新协议,提供了一个信息交换的安全隧道。这类暗网页面也是可以采集的。
遍历整个网站的数据采集的好处:
生成网站地图(脉络)
收集数据为了避免重复采集页面, 使用set来保存已采集的页面
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
pages = set();
def getLinks(pageUrl):
global pages;
html = urlopen("http://en.wikipedia.org"+pageUrl);
bs0bj = BeautifulSoup(html);
try:
print(bs0bj.h1.get_text());
print(bs0bj.find(id="mw-content-text").findAll("p")[0]);
print(bs0bj.find(id="ca-edit").find("span").find("a").attrs['href']);
except AttributeError :
print("页面缺少一些属性,不过不用担心!");
for link in bs0bj.findAll("a",href=re.compile("^(/wiki/)")):
if "href" in link.attrs:
if link.attrs["href"] not in pages:
#我们遇到新的页面
newPage = link.attrs['href'];
print("------------------------------------------");
pages.add(newPage);
getLinks(newPage);
getLinks("");
如果递归运行的次数过多, 递归程序可能会崩溃. python的默认递归限制是1000次.
在开始写爬虫程序之前, 应充分分析待爬取网站的html文档的格式
在一个异常处理语句中包裹多行语句显然是有点危险的. 首先无法识别出究竟哪行代码出现了异常, 其次前面的语句出现异常, 将直接导致后面语句的执行
网络爬虫位于许多新式的网络技术领域彼此交叉的中心地带. 要实现跨站的数据分析,只要构建出可以从互联网上的网页里解析和存储数据的爬虫就可以了
一个网站内部的爬虫, 只需要爬取以/开始的资源就可以了
不知前方水深浅
- 在开始写爬虫跟随外链随意跳转之前, 该思考的问题:
- 我要收集哪些数据?这些数据可以通过采集几个已经确定的网站(永远是最简单的做法)完成吗?或者我的爬虫 需要发现那些我可能不知道的网站吗?
- 当我的爬虫到了某个网站,它是立即顺着下一个出站链接跳到一个新网站,还是在网站上呆一会儿,深入采集网站的内容?
- 有没有我不想采集的一类网站?我对非英文网站的内容感兴趣吗?
- 如果我的网络爬虫引起了某个网站网管的怀疑,我如何避免法律责任?
在以任何正式目的运行代码之前, 确保已经在可能出现问题的地方都放置了检查语句
下面的代码可以到达互联网的任何位置(好刺激啊),毕竟网络世界良莠不齐,有些网站是禁止浏览的,所以阅读代码示例没问题,运行请小心。
from urllib.request import urlopen
from urllib.parse import urlparse
from bs4 import BeautifulSoup
import re
import datetime
import random
pages = set();
random.seed(datetime.datetime.now());
#获取页面所有内链
def getInternalLinks(bs0bj,includeUrl):
#urlparse(urlstring [, default_scheme [, allow_fragments]])的作用是将URL分解成不同的组成部分,它从urlstring中取得URL,并返回元组 (scheme, netloc, path, parameters, query, fragment)。注意,返回的这个元组非常有用,例如可以用来确定网络协议(HTTP、FTP等等 )、服务器地址、文件路径,等等。
includeUrl=urlparse(includeUrl).scheme+"://"+urlparse(includeUrl).netloc
print("scheme="+urlparse(includeUrl).scheme);
internalLinks = [];
#找到所有以‘/’开头的链接
for link in bs0bj.findAll("a",href=re.compile("^(/|.*)"+includeUrl+")")):
if link.attrs['href'] is not None:
if link.a ttrs['href'] not in internalLinks:
if(link.attrs['href'].startswish("/")):
internalLinks.append(includeUrl+link.attrs['href']);
else:
internalLinks.append(link.attrs['href']);
return internalLinks;
def getExertnalLinks(bs0bj,excludeUrl):
externalLinks =[];
#找到所有以'http'或'www'开头的链接
for link in bs0bj.findAll("a",href=re.compile("^(http|www)((?!"+excludeUrl+").)*$")):
if link.attrs['href'] is not None:
if link.attrs['href'] not in excludeUrl:
externalLinks.append(link.attrs['href']);
return externalLinks;
def getRandomExternaList(startingPage):
html = urlopen(startingPage);
bs0bj = BeautifulSoup(html);
externalLinks =getExertnalLinks(bs0bj,urlparse(startingPage).netloc);
if len(externalLinks) == 0:
print("No external links,looking around the site for one");
domain = urlparse(startingPage).scheme+"://"+urlparse(startingPage).netloc;
internalLinks=getInternalLinks(bs0bj,domain);
return getRandomExternaList(internalLinks[random.randint(0,len(internalLinks)-1)]);
else:
return externalLinks[random.randint(0,len(externalLinks)-1)];
def followExternalOnly(startingSite):
externalLink =getRandomExternaList(startingSite);
print("Random external link is: "+externalLink);
followExternalOnly(externalLink);
followExternalOnly("http://www.mmjpg.com/");