在我的上一篇文章里简单介绍了一下最简单的爬虫架构:《浅谈简单爬虫架构》
如下图所示
框架
mySpider
├─ spiderMain.py #爬虫调度端
├─ urlManager.py #URL管理器
├─ htmlDownloader.py #网页下载器
└─ htmlParser.py #网页解析器
此篇以爬取廖雪峰的官方网站中的python教程为例
不过廖老师的网站对爬虫进行了过滤,建议举一反三,尝试爬取其他网站
现在的网络爬虫越来越多,有很多爬虫都是初学者写的,和搜索引擎的爬虫不一样,他们不懂如何控制速度,结果往往大量消耗服务器资源,导致带宽白白浪费了。
其实Nginx可以非常容易地根据User-Agent过滤请求,我们只需要在需要URL入口位置通过一个简单的正则表达式就可以过滤不符合要求的爬虫请求:
...
location / {
if ($http_user_agent ~* "python|curl|java|wget|httpclient|okhttp") {
return 503;
}
# 正常处理
...
}
...
变量$http_user_agent是一个可以直接在location中引用的Nginx变量。~*表示不区分大小写的正则匹配,通过python就可以过滤掉80%的Python爬虫
爬虫调度端
爬虫调度端的核心代码实现:
while urlManager.hasUrl(): #询问url管理器是否有待爬url
newurl = urlManager.getUrl() #获取待爬url
html = htmlDownloader.download(newurl,headers) #下载页面
(title,content),urls = htmlParser.parser(html) #从页面中解析出内容和新的url
#此处可以处理爬下来的文件,比如储存到本地,我仅打印标题以测试
print(title)
urlManager.addUrls(urls) #将新的url加入URL管理器
其中的headers是请求头,用于模拟浏览器的行为
可以使用chrome浏览器的开发者工具找到
网页右击 检查->network
刷新页面
完整代码:
import requests
from urlManager import UrlManager #url管理器
from htmlDownloader import HtmlDownloader #页面下载器
from htmlParser import HtmlParser #网页解析器
#请求头
headers={'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','Accept-Encoding':'gzip, deflate, br','Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8','Cache-Control':'max-age=0','Connection':'keep-alive','Cookie':'Hm_lvt_2efddd14a5f2b304677462d06fb4f964=1516595211; atsp=1516853801496_1516853801154; Hm_lpvt_2efddd14a5f2b304677462d06fb4f964=1516853818','Host':'www.liaoxuefeng.com','Referer':'https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318447437605e90206e261744c08630a836851f5183000','Upgrade-Insecure-Requests':'1','User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
if __name__ == '__main__':
print('爬虫开始...')
urlManager = UrlManager()
htmlDownloader = HtmlDownloader()
htmlParser = HtmlParser()
urlManager.addUrl('https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000')
while urlManager.hasUrl():
newurl = urlManager.getUrl()
if newurl != "":
html = htmlDownloader.download(newurl,headers)
(title,content),urls = htmlParser.parser(html)
print(title)
#文件处理储存
urlManager.addUrls(urls)
URL管理器
url管理器可以使用queue或set甚至list来实现,如果需要按顺序爬取,可以使用队列来实现,即先加入url管理器的url先爬取,但是需要注意的是使用队列则需要检查待加入的url是否已经在队列中了。
而如果对顺序没有特别的要求,使用set更为简便,可以直接加入待爬url,因为重复的元素只会在set中出现一次。
但无论使用queue、set还是list实现,都需要检查待加的url是否已经爬过了,以此避免重复爬取甚至循环爬取
完整代码:
class UrlManager(object):
def __init__(self):
self._newUrls = set([]) #set newUrls中储存未访问的url
self._oldUrls = set([]) #set oldUrls中储存已访问的url
def addUrl(self,url): #添加url
if isinstance(url,str) and url not in self._oldUrls:
self._newUrls.add(url)
def hasUrl(self): #是否还有未访问的url
return len(self._newUrls) > 0
def getUrl(self): #该函数返回url视为已访问
if not self._newUrls: #不存在新的url
return ""
url = self._newUrls.pop()
self._oldUrls.add(url)
return url
def addUrls(self,urls):
if isinstance(urls,list):
for url in urls:
if url not in self._oldUrls:
self._newUrls.add(url)
网页下载器
网页下载器十分简单,在此使用了request模块
详情可以查看request模块文档
完整代码:
import requests
class HtmlDownloader(object):
def download(self,url,headers=""): #url:待爬链接 headers:请求头 return value:html文本
if url is None or not isinstance(url,str):
#获取失败错误处理
return None
r = requests.get(url,headers=headers)
return r.text
网页解析器
网页解析器使用了BeautifulSoup模块,非常方便快捷
具体参考BeautifulSoup文档
像我就记不太住,都是随用随查的,所以这是最费时间的一部分,也很容易出错(逃
这里需要我们自己分析页面来解析
完整代码:
from bs4 import BeautifulSoup
baseUrl = 'https://www.liaoxuefeng.com'
class HtmlParser(object):
def parser(self,text): #text: html文本 return value: (需要的数据,list[需要的url])
soup = BeautifulSoup(text,'lxml')
#获取标题
title = soup.h4
#print(soup.select(".x-main-content"))
if len(soup.select(".x-main-content")) >0:
# print("True")
content = soup.select(".x-main-content")[0].get_text()
else:
# print("False")
content=""
urls = []
for a in soup.find_all('a',"x-wiki-index-item"):
urls.append(baseUrl+a.get('href'))
return ((title,content),urls)
价值数据
做到这里,我们就已经可以获取到我们想要的价值数据了。
在这里我仅打印标题以测试,实际上我们可以对数据做更多的处理,而这就需要我们继续深入学习了。共勉!