阅读目录
目的意义
基础爬虫分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则结束。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711?fr=aladdin" ) print ( 'finish' ) |
作为最初的设计,应该允许异常抛出,便于查看程序终止的原因,然后排查错误。
HTML下载器设计
下载网页,返回文本。即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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和获取数据文本的信息。
为了处理一些不同网页可能抓取的意外情况导致程序终止,添加了一些判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
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管理方案,如提取,数量判定等,也在这里实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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;'参数。参阅:https://zhidao.baidu.com/question/1385859725784504260.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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() |
最终效果:
当然还有一些数据没有处理好。