目的意义
基础爬虫分 5 个模块, 使用多个文件相互配合, 实现一个相对完善的数据爬取方案, 便于以后更完善的爬虫做准备.
这里目的是爬取 200 条百度百科信息, 并生成一个 html 文件, 存储爬取的站点, 词条, 解释.
本文思路来源书籍. 其代码部分来源书籍. https://book.douban.com/subject/27061630/
功能模块
主文件: 爬虫调度器, 通过调用其他文件中的方法, 完成最终功能实现.
其他文件: URL 管理器, HTML 下载器, HTML 解析器, 数据存储器.
设计思路
定义 SpiderMan 类作为爬虫调度器. 输入根 URL 开始爬取数据然后爬取结束.
在爬取过程中, 需要获取网页, 和解析网页.
解析网页需要 HTML 解析器, 获取网页需要 HTML 下载器.
解析网页需要解析的数据有: URL,TITLE,CONTEXT 等. 则需要 URL 管理器和数据存储器.
主文件设计
主文件添加根 URL, 然后提取该 URL, 下载该 URL 内容.
根据内容, 调用解析器:
解析出该 URL 中的新 URL, 存入 URL 管理器;
解析出该 URL 中的标题, 文本等信息, 存入数据存储器.
完成后开始下一次. 这时 URL 管理器多出了新的 URL, 提取出新的 URL, 下载, 解析, 不断重复即可.
重复结束以提取出的 URL 数量超过 200 则结束.
代码如下:
- from BaseSpider.DataOutput import DataOutput
- from BaseSpider.HtmlDownloader import HtmlDownloader
- from BaseSpider.HtmlParser import HtmlParser
- from BaseSpider.UrlManager import UrlManager
- class SpiderMan():
- def __init__(self):
- self.manager=UrlManager()
- self.downloader=HtmlDownloader()
- self.parser=HtmlParser()
- self.output=DataOutput()
- def crawl(self,root_url):
- self.manager.add_new_url(root_url)
- while(self.manager.has_new_url() and self.manager.old_url_size()<200):
- new_url=self.manager.get_new_url()
- text=self.downloader.download(new_url)
- if text is None:
- print('None text')
- break
- new_urls,data=self.parser.parser(new_url,text)
- self.manager.add_new_urls(new_urls)
- self.output.store_data(data)
- print(self.manager.old_url_size())
- self.output.output_html()
- if __name__ == "__main__":
- spider_man=SpiderMan()
- spider_man.crawl("https://baike.baidu.com/item/网络爬虫/5162711?fr=aladdin")
- print('finish')
作为最初的设计, 应该允许异常抛出, 便于查看程序终止的原因, 然后排查错误.
HTML 下载器设计
下载网页, 返回文本. 即可.
- import requests
- import chardet
- class HtmlDownloader(object):
- def download(self,url):
- if url is None:
- return None
- user_agent='Mozilla/5.0 (Windows NT 10.0; WOW64) ApplewebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0'
- headers={'User-Agent':user_agent}
- r=requests.get(url,headers=headers)
- if r.status_code is 200:
- r.encoding=chardet.detect(r.content)['encoding']
- return r.text
- return None
HTML 解析器设计
HTML 解析器将下载的文本进行解析, 需要解析出的数据有: 页面的新 URL, 页面的新数据文本.
建立相应的解析器, 需要打开源码对比, 然后进行使用源码分析, 使用 BeautifulSoup 获取所需信息.
为了便于主函数调用或者其他原因, 将所有数据通过 parser 实现返回, 其 parser 分别调用获取 URL 和获取数据文本的信息.
为了处理一些不同网页可能抓取的意外情况导致程序终止, 添加了一些判断.
- import re
- from urllib import parse
- from bs4 import BeautifulSoup
- class HtmlParser(object):
- def parser(self,page_url,html_cont):
- if page_url is None or html_cont is None:
- return
- soup=BeautifulSoup(html_cont,'lxml')
- new_urls=self.getNewUrls(page_url,soup)
- new_data=self.getNewData(page_url,soup)
- return new_urls,new_data
- def getNewUrls(self,page_url,soup):
- new_urls=set()
- links=soup.find_all('a',href=re.compile(r'/item/.*'))
- for link in links:
- new_url=link['href']
- new_full_url=parse.urljoin(page_url,new_url)
- new_urls.add(new_full_url)
- return new_urls
- def getNewData(self,page_url,soup):
- data={}
- data['url']=page_url
- title=soup.find('dd',class_="basicInfo-item value")
- if title is not None:
- data['title']=title.string
- summary=soup.find('meta',attrs={"name":"description"})
- data['summary']=summary['content']
- return data
- else:
- title=soup.find('meta',attrs={"name":"keywords"})
- if title is not None:
- data['title']=title['content']
- summary=soup.find('meta',attrs={"name":"description"})
- data['summary']=summary['content']
- return data
- else:
- data['title']="ERROR!"
- data['summary']="Please check the url for more information"
- data['url']=page_url
- return data
URL 管理器设计
为了避免重复的 URL, 使用 python 的 set, 建立集合初始化. 参阅: https://www.runoob.com/python3/python3-set.html
使用 old_urls 存储已经访问过的网址, 使用 new_urls 存入将要提取的网址.
然后写好 has_new_url 等方法, 辅助主程序调用. 当得到新的 URL 们时, 主程序调用函数将他们存入.
而主程序需要的其他 URL 管理方案, 如提取, 数量判定等, 也在这里实现.
- class UrlManager():
- def __init__(self):
- self.old_urls=set()
- self.new_urls=set()
- pass
- def has_new_url(self):
- return self.new_url_size()!=0
- def new_url_size(self):
- return len(self.new_urls)
- def old_url_size(self):
- return len(self.old_urls)
- def get_new_url(self):
- new_url=self.new_urls.pop()
- self.old_urls.add(new_url)
- return new_url
- def add_new_url(self,url):
- if url is None:
- return
- if url not in self.new_urls and url not in self.old_urls:
- self.new_urls.add(url)
- pass
- def add_new_urls(self,urls):
- if urls is None or len(urls) == 0:
- return
- for url in urls:
- self.add_new_url(url)
- pass
数据存储器设计
通过 HTML 解析器获取的数据, 通过数据存储器进行存储.
而最终将数据从内存写入到本地磁盘, 也在该文件实现.
为了调试美观, 建议是先爬取一两个数据做好测试, 写好 table 的宽度设定, 加入 style='word-break:break-all;word-wrap:break-word;'参数. 参阅:
- import codecs
- class DataOutput(object):
- def __init__(self):
- self.datas=[]
- def store_data(self,data):
- if data is None:
- return
- self.datas.append(data)
- def output_html(self):
- fout=codecs.open('baike.html', 'w', encoding='utf-8')
- fout.write("<html>")
- fout.write("<head><meta charset='urf-8'></head>")
- fout.write("<body>")
- fout.write("<table border='1'width=1800 style='word-break:break-all;word-wrap:break-word;'>")
- fout.write("<tr>")
- fout.write("<td width='300'>URL</td>")
- fout.write("<td width='100'> 标题 </td>")
- fout.write("<td width='1200'> 释义 </td>")
- fout.write("</tr>")
- for data in self.datas:
- fout.write("<tr>")
- fout.write("<td><a href=%s>%s</a></td>"%(data['url'],data['url']))
- fout.write("<td>%s</td>"%data['title'])
- fout.write("<td>%s</td>"%data['summary'])
- fout.write("</tr>")
- fout.write("</table>")
- fout.write("</body>")
- fout.write("</html>")
- fout.close()
最终效果:
当然还有一些数据没有处理好.
完
来源: https://www.cnblogs.com/bai2018/p/10994735.html