一、正则表达式和BeautifulSoup
1.利用正则表达式查找所需内容
通过正则表达式与BeautifulSoup库的结合使用,可以使查找所需内容的工作更加灵活。
bsObj.findAll("img",{"src":re.compile("\.\.\/img\/difts\/img.*\.jpg")})
#查找条件为: ../img/difts/ img.*.jpg
../img/difts/(到此为目录) img.*(所有图片都以img开头,之后为任意字符) .jpg(都以jpg结尾)
例:查找赶集网宠物网页中,带有“?”的句子
import urllib.request as ur
from bs4 import BeautifulSoup
import re
html = ur.urlopen("http://bj.ganji.com/chongwu/")
bsObj = BeautifulSoup(html.read(),"html.parser")
links = bsObj.findAll("a",{"title":re.compile("?$")})
for link in links:
if "title" in link.attrs:
print(link.attrs["title"])
结果:
被狗狗咬了如何处理?
如何给萨摩狗狗买玩具?
萨摩耶犬好养吗?
雪纳瑞好养吗?
2.利用attrs获取属性
对于一个标签,我们可以利用attrs获取它的全部属性,也可以获取单个属性。获取全部属性时返回字典
例:查找标签属性个数为2的所有标签
import urllib.request as ur
from bs4 import BeautifulSoup
import re
html = ur.urlopen("http://bj.ganji.com/chongwu/")
bsObj = BeautifulSoup(html.read(),"html.parser")
links = bsObj.findAll("a",{"title":re.compile("?$")})
for link in links:
if "title" in link.attrs:
print(link.attrs)
print(link.attrs["title"])
结果:
{'href': 'http://www.ganji.com/mao/11012515-28127.htm', 'target': '_blank', 'title': '教您如何挑选猫猫?'}
教您如何挑选猫猫?
{'href': 'http://www.ganji.com/mao/11012515-28130.htm', 'target': '_blank', 'title': '被猫抓了如何处理?'}
被猫抓了如何处理?
{'title': '如何给萨摩狗狗买玩具?', 'target': '_blank', 'href': 'http://www.ganji.com/zhishi/news_4403.htm'}
如何给萨摩狗狗买玩具?
{'title': '萨摩耶犬好养吗?', 'target': '_blank', 'href': 'http://www.ganji.com/zhishi/news_4410.htm'}
萨摩耶犬好养吗?
2.Lambda表达式
Lambda表达式可以将函数作为参数进行查找
import urllib.request as ur
from bs4 import BeautifulSoup
import re
html = ur.urlopen("http://bj.ganji.com/chongwu/")
bsObj = BeautifulSoup(html.read(),"html.parser")
links = bsObj.findAll(lambda a:len(a.attrs)==2)
for link in links:
print(link.attrs)
结果:
{'http-equiv': 'Content-Type', 'content': 'text/html; charset=utf-8'}
{'name': 'renderer', 'content': 'webkit'}
{'name': 'keywords', 'content': '北京宠物网,北京宠物店,北京宠物市场,北京网上宠物市场'}
{'name': 'description', 'content': '赶集网北京宠物网是专业的北京网上宠物交易市场,您可以免费查看发布北京宠物买卖、宠物赠送、宠物领养、宠物交友、宠物店、宠物用品批发等信息。'}
{'name': 'mobile-agent', 'content': 'format=html5; url=http://3g.ganji.com/bj_pet/'}
{'name': 'mobile-agent', 'content': 'format=xhtml; url=http://wap.ganji.com/bj/pet/'}
{'name': 'location', 'content': 'province=北京;city=北京'}
{'src': '//sta.ganjistatic1.com/public/js/util/ganji/ganji.__1562407200__.js', 'type': 'text/javascript'}
{'data-widget': 'app/ms_v2/common/base_page.js#topNav', 'class': ['top-nav', 'fl']}
{'class': ['ganji-dingdong'], 'data-widget': 'app/ms_v2/common/base_page.js#hoverWidget'}
{'class': ['column', 'ganji-dingdong'], 'data-widget': 'app/ms_v2/common/base_page.js#hoverWidget'}
{'data-widget': 'app/ms_v2/common/base_page.js#hoverWidget', 'class': ['top-nav', 'fl']}
{'class': ['top-nav-btn', 'clearfix', 'js-btn'], 'href': 'http://bj.ganji.com/tuiguang/'}
{'gjalog': '/self_service/trace@source=self_promotion_post_index_header@city_id=12@cat=chongwu@atype=click', 'href': 'http://bj.ganji.com/tuiguang/zhaopin/bangbang/'}
{'gjalog': '/self_service/trace@source=self_promotion_post_index_header@city_id=12@cat=chongwu@atype=click', 'href': 'http://bj.ganji.com/tuiguang/fang/bangbang/'}
{'gjalog': '/self_service/trace@source=self_promotion_post_index_header@city_id=12@cat=chongwu@atype=click', 'href': 'http://bj.ganji.com/tuiguang/fuwu/bangbang/'}
{'gjalog': '/self_service/trace@source=self_promotion_post_index_header@city_id=12@cat=chongwu@atype=click', 'href': 'http://bj.ganji.com/tuiguang/che/bangbang/'}
{'gjalog': '/self_service/trace@source=self_promotion_post_index_header@city_id=12@cat=chongwu@atype=click', 'href': 'http://bj.ganji.com/tuiguang/wu/bangbang/'}
{'gjalog': '/self_service/trace@source=self_promotion_post_index_header@city_id=12@cat=chongwu@atype=click', 'href': 'http://bj.ganji.com/tuiguang/chongwu/bangbang/'}
{'class': ['fl', 'reg-login'], 'data-widget': 'app/ms_v2/common/base_page.js#userinfoWidget'}
{'class': ['h-search'], 'data-widget': 'app/ms_v2/common/list_page.js#fixSearchBar'}
{'class': ['logo-2013'], 'href': '/'}
{'class': ['sear-menu0-con'], 'data-role': 'options'}
{'href': '###', 'title': '宠物'}
{'href': '###', 'title': '所有分类'}
{'id': 'wrapper', 'class': ['pet-categroy', 'clearfix']}
{'href': '/chongwupeizhong/3611518915x.htm', 'target': '_blank'}
{'href': '/chongwupeizhong/3603555111x.htm', 'target': '_blank'}
{'target': '_self', 'href': '#floor1'}
{'target': '_self', 'href': '#floor2'}
{'target': '_self', 'href': '#floor3'}
{'target': '_self', 'href': '#floor4'}
{'target': '_self', 'href': '#floor5'}
{'target': '_self', 'href': '#floor6'}
{'target': '_self', 'href': '#floor7'}
{'name': 'floor1', 'id': 'floor1'}
{'href': '/gou/', 'target': '_blank'}
{'href': '/chongwushipin/', 'target': '_blank'}
{'href': '/chongwurichangyongju/', 'target': '_blank'}
{'href': '/chongwupeizhong/', 'target': '_blank'}
{'href': '/chongwufuwu/', 'target': '_blank'}
{'href': '/chongwujiyang/', 'target': '_blank'}
{'href': '/chongwumeirong/', 'target': '_blank'}
{'href': '/chongwuyiyuan/', 'target': '_blank'}
{'href': '/taidixiong/', 'target': '_blank'}
{'href': '/zangao/', 'target': '_blank'}
{'href': '/jinmao/', 'target': '_blank'}
{'href': '/hashiqi/', 'target': '_blank'}
{'href': '/deguomuyangquan/', 'target': '_blank'}
{'href': '/bomeiquan/', 'target': '_blank'}
{'href': '/samoquan/', 'target': '_blank'}
{'href': '/abuladuo/', 'target': '_blank'}
{'href': '/gou/', 'target': '_blank'}
{'name': 'floor2', 'id': 'floor2'}
{'href': '/mao/', 'target': '_blank'}
{'href': '/maoliang/', 'target': '_blank'}
{'href': '/maoyongpin/', 'target': '_blank'}
{'href': '/chongwupeizhong/', 'target': '_blank'}
{'href': '/chongwufuwu/', 'target': '_blank'}
{'href': '/chongwujiyang/', 'target': '_blank'}
{'href': '/chongwumeirong/', 'target': '_blank'}
{'href': '/chongwuyiyuan/', 'target': '_blank'}
{'href': '/mao/', 'target': '_blank'}
{'name': 'floor3', 'id': 'floor3'}
{'href': '/guanshangyu/', 'target': '_blank'}
{'href': '/yusiliao/', 'target': '_blank'}
{'href': '/boliyugang/', 'target': '_blank'}
{'href': '/shuizufuwu/', 'target': '_blank'}
{'href': '/guanshangyu/', 'target': '_blank'}
{'name': 'floor4', 'id': 'floor4'}
{'href': '/yingwu/', 'target': '_blank'}
{'href': '/bage/', 'target': '_blank'}
{'href': '/gezi/', 'target': '_blank'}
{'href': '/niao/', 'target': '_blank'}
{'name': 'floor5', 'id': 'floor5'}
{'href': '/qitaxiaochong/', 'target': '_blank'}
{'href': '/longmao/', 'target': '_blank'}
{'href': '/gui/', 'target': '_blank'}
{'href': '/cangshu/', 'target': '_blank'}
{'href': '/chongwuzhu/', 'target': '_blank'}
{'href': '/qitaxiaochong/', 'target': '_blank'}
{'name': 'floor6', 'id': 'floor6'}
{'href': '/chongwushipin/', 'target': '_blank'}
{'href': '/chongwurichangyongju/', 'target': '_blank'}
{'href': '/maoliang/', 'target': '_blank'}
{'href': '/maoyongpin/', 'target': '_blank'}
{'href': '/boliyugang/', 'target': '_blank'}
{'href': '/chongwuwanju/', 'target': '_blank'}
{'href': '/chongwuyongpin/', 'target': '_blank'}
{'href': '/chongwuyiyuan/', 'target': '_blank'}
{'href': '/chongwujiyang/', 'target': '_blank'}
{'href': '/chongwumeirong/', 'target': '_blank'}
{'href': '/shuizufuwu/', 'target': '_blank'}
{'href': '/chongwufuwu/', 'target': '_blank'}
{'name': 'floor7', 'id': 'floor7'}
{'href': '/chongwujiuzhu/', 'target': '_blank'}
{'href': '/jiayangzengsong/', 'target': '_blank'}
{'href': '/qiuyangchongwu/', 'target': '_blank'}
{'href': '/free/', 'target': '_blank'}
{'class': ['backtotop', ''], 'data-widget': 'app/ms_v2/common/base_page.js#sideFloatWidget'}
{'target': '_blank', 'href': '/chongwuliebiao.htm'}
{'href': 'http://sh.ganji.com/chongwu/', 'target': '_blank'}
{'href': 'http://longyu.cc/', 'target': '_blank'}
{'href': 'http://www.apetdog.com/', 'target': '_blank'}
{'href': 'http://www.epet.com/', 'target': '_blank'}
{'href': 'http://bj.58.com/chongwu/', 'target': '_blank'}
{'target': '_blank', 'href': 'http://bj.ganji.com/quxiandaohang/'}
{'target': '_blank', 'href': 'http://wap.ganji.com/bj/pet/'}
{'target': '_blank', 'href': 'http://3g.ganji.com/bj_pet/'}
二、遍历单个域名
爬虫的本质就是一种递归方式。为了找到URL链接,它们必须首先获取网页内容,检查这个页面的内容,在寻找另外一个URL,然后后获取URL对应的网页内容,不断循环这一过程。根据“维基百科六度分隔理论”的查找方法,从张云雷百度词条页面通过最少的点击链接,找到岳云鹏的词条页面
import urllib.request as ur
from bs4 import BeautifulSoup
html = ur.urlopen("https://baike.baidu.com/item/%E5%BC%A0%E4%BA%91%E9%9B%B7/17149")
bsObj = BeautifulSoup(html,"html.parser")
for link in bsObj.findAll("a"):
if 'href' in link.attrs:
print(link.attrs['href'])
你会发现结果为一系列的链接,其中有些是我们需要的,有些不是我们需要的。其实百度百科的每个页面都充满了侧边栏、页眉、以及连接到分类页面、对话页面和其他不包含词条的页面的链接。经过对采集页面的分析,发现那些指向词条页面(不是指向其他内容页面)的链接,会发现他们有两个个特点:
-
他们都在dd标签中
-
class属性值都是basicInfo-item value
利用这些,我们可以稍微调整一下代码:
import urllib.request as ur
from bs4 import BeautifulSoup
html = ur.urlopen("https://baike.baidu.com/item/%E5%BC%A0%E4%BA%91%E9%9B%B7/17149")
bsObj = BeautifulSoup(html,"html.parser")
for link in bsObj.findAll("dd",{"class":"basicInfo-item value"}):
url = link.find("a")
if url != None:
print(url.attrs["href"])
在实际应用中,我们需要让代码符合以下要求:
1.一个函数getLinks,可以用百度百科链接+分页面路径(https://baike.baidu.com + /item/%E5%BC%A0%E4%BA%91%E9%9B%B7/17149)形式的URL链接作为参数,然后以同样的形式返回一个列表,其中包含所有的词条URL链接。
2. 一个主函数,以某个起始词条为参数调用getLinks,再从返回的URL列表里随机选择一个词条链接,再重复调用getLinks,直到我们主动停止,或者在新的界面上没有词条链接了,程序才停止执行。
import urllib.request as ur
from bs4 import BeautifulSoup
import datetime #导入随机数生成器
import random
random.seed(datetime.datetime.now()) #生成随机数
def getLinks(url):
html = ur.urlopen(url)
bsObj = BeautifulSoup(html,"html.parser")
links = []
for link in bsObj.findAll("dd",{"class":"basicInfo-item value"}):
url = link.find("a")
if url != None:
links.append(url.attrs["href"])
return links
url = "https://baike.baidu.com/item/%E5%BC%A0%E4%BA%91%E9%9B%B7/17149"
ls = getLinks(url)
while len(ls)>0:
newArticle = "https://baike.baidu.com"+ls[random.randint(0,len(ls)-1)]
print(newArticle)
ls = getLinks(newArticle)
程序中用系统当前时间生成一个随机数生成器 ,保证在每次运行的时候,百度百科词条的选择都是一个全新的随机路径。
三、采集整个网站
一个常用的网站采集方法就是从顶级页面开始,然后搜索页面上的所有链接,形成列表。再去采集这些链接的每一个页面,然后把在每个页面上找到的链接形成新的列表,重复执行下一轮采集。 但在采集过程中,不可避免的会将一些网页重复采集,因此需要对链接进行去重处理。
import urllib.request as ur
from bs4 import BeautifulSoup
import re
pages = set()
def getLinks(url):
global pages
html = ur.urlopen("https://baike.baidu.com"+url)
bsObj = BeautifulSoup(html,"html.parser")
print(bsObj.title.get_text())
for dd in bsObj.findAll("dd",{"class":"basicInfo-item value"}):
alinks = dd.findAll("a")
for alink in alinks:
if "href" in alink.attrs:
if alink.attrs["href"] not in pages:
#我们遇到了新页面
newPage = alink.attrs["href"]
print(newPage)
pages.add(newPage)
getLinks(newPage)
url = "/item/%E5%BC%A0%E4%BA%91%E9%9B%B7/17149"
getLinks(url)
注:如果递归运行的次数非常多,前面的递归程序就很可能崩溃。python默认的递归限制是1000次。因为百度百科网络链接体量极大,所以这个程序达到递归限制后就会停止。解决方法为设置一个较大的递归计数器,或用其他手段不让它停止。
四、通过互联网采集
当爬取整个互联网时,需要关注外链接的存在,外链接的特点一般是以http,https和www开头。但是跟随外链接跳转之前,需要注意一下问题:
-
外链接可能会链接到一些宗教或黄色网站,需要进行排除和筛选
-
明确要收集哪些数据,避免获取大量无用数据
-
当爬取到某个网站的时候,是直接链接到一个新的网站还是在当前网站暂停
-
如果引起了某网站网关的怀疑,如何避免法律责任。
import urllib.request as ur
from bs4 import BeautifulSoup
import re
pages = set()
#获取内链接
def getLinks(url):
global pages
html = ur.urlopen("https://baike.baidu.com"+url)
bsObj = BeautifulSoup(html,"html.parser")
for dd in bsObj.findAll("dd",{"class":"basicInfo-item value"}):
alinks = dd.findAll("a")
for alink in alinks:
if "href" in alink.attrs:
if alink.attrs["href"] not in pages:
#我们遇到了新页面
newPage = alink.attrs["href"]
print(newPage)
pages.add(newPage)
getLinks(newPage)
pages0 = set()
def getLinks0(url):
global pages0
html = ur.urlopen("https://baike.baidu.com" + url)
bsObj = BeautifulSoup(html, "html.parser")
for link in bsObj.findAll("a", href = re.compile("^(http|https|www)")):
if link.attrs["href"] not in pages0:
newPage = link.attrs["href"]
print(newPage)
pages0.add(newPage)
getLinks0(newPage)
url = "/item/%E5%BC%A0%E4%BA%91%E9%9B%B7/17149"
getLinks(url)
getLinks0(url)