看了网上好多人写的爬虫,架构风格都不是很喜欢,前几天在GitHub上翻到一个项目,主要是结构特别好,那种面向对象的风格很受我的喜欢,今天按照这种方式写了两个爬虫分享给大家
废话不多说,直接上代码
一.利用requests,BeautifulSoup库爬取CSDN上的1000篇博客
一共四个文件:
1.spider_mian:调度器
import re
from CSDN_spider import html_parser, save_txt, html_downloader
#爬虫总调度器
class SpiderMain(object):
#构造方法初始化下载器,解析器,存储器
def __init__(self):
self.downloader = html_downloader.Downloader()
self.html_parser = html_parser.Parser()
self.saver = save_txt.Saver()
#爬虫方法,构造参数:开始网址,要爬取的文章数量,和页面号
def crawl(self,root_link,article_count,page_num):
count = 1
link = root_link
#循环迭代,直到爬到一定数量的文章
while 1:
#通过下载器,解析器得到网页上所有文章链接
page = self.downloader.download(link)
urls = self.html_parser.parse_link(page)
#对每个文章的连接进行爬虫
for url in urls:
print('crawl %d:%s' % (count ,url))
article_html = self.downloader.download(url)
article_done = self.html_parser.parse_article(article_html)
#判断文章为空,则不进行存储
if article_done is not None:
self.saver.save_article(article_done,count,url)
count += 1
if count==article_count:
break
if count == article_count:
break
#进行下一次的爬取
page_num += 1
new_link = re.sub(r'p=\d+','p=%d'%page_num,link)
link = new_link
if __name__ == '__main__':
# root_link = 'http://so.csdn.net/so/search/s.do?p=1&q=Lucene&t=blog&domain=&o=&s=&u=&l=&f=&rbg=0'
# root_link = ' http://so.csdn.net/so/search/s.do?p=2&q=%E5%A4%A7%E6%95%B0%E6%8D%AE&t=blog&domain=&o=&s=&u=&l=&f=&rbg=0'
root_link = 'http://so.csdn.net/so/search/s.do?p=1&q=%E4%BA%91%E8%AE%A1%E7%AE%97&t=blog&o=&s=&l='
article_count = 1000
page_num = 1
spider = SpiderMain()
spider.crawl(root_link,article_count,page_num)
将爬虫的总调度程度单独写在一个类中,这样可维护性,可读性都比较好
2.html_downloader:下载器
import requests
class Downloader(object):
#下载方法
def download(self, url):
if url is None or len(url)==0:
return None
try:
#注意要改变头信息
r = requests.get(url,headers={'user-Agent':'Mozilla/5.0'})
r.raise_for_status()
r.encoding=r.apparent_encoding
return r.text
except:
print('download error')
3.html_parser:解析器
import re
from bs4 import BeautifulSoup
class Parser(object):
#解析网页的方法
def parse_link(self, page):
#这里养成习惯做判断
if page is None:
return None
soup = BeautifulSoup(page,'html.parser')
#用beautifulsoup+正则获取链接
links = soup.find_all('a',href=re.compile(r'.*/article/details/\d*'))
#用set防止重复链接
urls = set()
for link in links:
urls.add(link['href'])
return urls
#解析文章的方法
def parse_article(self, article_html):
if article_html is None:
return None
try:
#分别解析文章的标题和内容,以字典的形式存储
soup = BeautifulSoup(article_html, 'html.parser')
article_done = {}
title = soup.find('h1',class_='csdn_top').get_text()
article_done['title'] = title
content = soup.find('div',id='article_content').get_text()
article_done['content'] = content
return article_done
except:
return None
4.Saver:存储器
class Saver(object):
def save_article(self, article_done,num,url):
#注意改变编码
with open('./article/'+str(num)+'.txt','w',encoding='utf-8') as f:
f.writelines('title:'+article_done['title']+'\n')
f.writelines('url:'+url+'\n')
f.writelines('content:'+article_done['content'])
我都在代码中写了详细的注释,应该能看懂
5.踩过的坑儿
这个爬虫很简单,写的话用不了多长时间,但是还是有一些细节问题让我调试程序也花费了好多时间
1.在每个解析或下载的方法中,一定要判断传进来的参数是否为空,否则很可能发生异常,引发爬虫中断。
2.容易引发异常的地方用try except 处理,防止爬虫中断
3.装入url一定要用set!因为一个页面中可能有不同的url,这个我真的查了好久才发现
4.爬虫的时候要有良好的用户提示,输出进度,一直等着很蛋疼!
5.注意修改头信息,有的网站反爬虫技术,不让爬,这个要养成习惯
6.注意修改爬虫response对象编码,具体参照我上面的代码
7.要修改输出文件的编码,utf-8
8.养成良好的代码风格,将不同功能的模块分离开,就像上面我写的这种,一个类负责一个功能模块
二.爬取百度百科的1000个词条信息
1.spider_main
class SpiderMain(object):
#初始化信息,这里加一个url管理器
def __init__(self):
self.urls = url_manager.UrlManager()
self.downloader = html_downloader.Downloader()
self.parser = html_parser.Parser()
self.outputer = html_outputer.Outputer()
def crawl(self,root_url):
#将根路径添加到url管理器中
self.urls.add_url(root_url)
count = 1
#迭代爬取,直到满足条件
while self.urls.has_new_url():
#从url管理器得到新的url
new_url = self.urls.get_new_url()
#输出进度
print('crawl %d:%s'%(count,new_url))
#下载器下载内容
html_cont = self.downloader.download(new_url)
#解析器解析内容
new_urls ,new_data = self.parser.parse(new_url,html_cont)
#将解析的url添加到url管理器中
self.urls.add_urls(new_urls)
#将解析后的内容加入处理器
self.outputer.collect(new_data)
if(count==1000):
break
count += 1
self.outputer.output()
if __name__ == '__main__':
root_url = 'https://baike.baidu.com/item/蜘蛛/6152';
obj_spider = SpiderMain()
obj_spider.crawl(root_url)
2.html_downloader
这部分代码跟上面没有太大的变化,就不写注释了
class Downloader(object):
def download(self, new_url):
if new_url is None or len(new_url)==0:
return None
try:
r = requests.get(new_url,headers={'user-Agent':'Mozilla/5.0'})
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
print('download error')
3.url_manager
这里新加了个url管理器,这个类负责对所有的类进行管理,这样就可以迭代进行爬取,我觉得有点像广度优先搜索的意思,只不过这里我们用一个集合来维护
class UrlManager(object):
def __init__(self):
#注意这里要用集合,防止有同样的url
self.new_urls = set()
self.old_urls = set()
#取出url
def get_new_url(self):
url = self.new_urls.pop()
self.old_urls.add(url)
return url
#判断是否有新的url
def has_new_url(self):
return len(self.new_urls) != 0
#添加新的url
def add_urls(self, new_urls):
if new_urls is None or len(new_urls)==0:
return
for url in new_urls:
self.new_urls.add(url)
#添加原始url
def add_url(self, root_url):
if root_url is None:
return
if root_url not in self.new_urls and root_url not in self.old_urls:
self.new_urls.add(root_url)
4.html_parser
import re
from urllib.parse import urljoin
from bs4 import BeautifulSoup
class Parser(object):
#解析url,解析出新的url和要爬取的内容,这里为了解耦也将这两个方法分开写
def parse(self, new_url, html_cont):
if new_url is None or html_cont is None:
return
soup = BeautifulSoup(html_cont,'html.parser')
new_urls = self.get_urls(soup,new_url)
new_data = self.get_data(soup, new_url)
return new_urls,new_data
def get_urls(self, soup, url):
new_urls = set()
links = soup.find_all('a',href=re.compile(r'/item/(.*)'))
#对爬取的链接循环加入
for link in links:
new_url = link['href']
#这个方法很好用,自动拼接,推荐大家使用
full_url = urljoin(url,new_url)
new_urls.add(full_url)
return new_urls
# < dd class ="lemmaWgt-lemmaTitle-title" >
#<div class="lemma-summary" label-module="lemmaSummary">
def get_data(self, soup, new_url):
#用字典存储
data = {}
title_node = soup.find('dd',class_="lemmaWgt-lemmaTitle-title").find('h1')
#bs提供了大量的方法来获取你想要的内容
data['title'] = title_node.get_text()
summary_node = soup.find('div',class_='lemma-summary')
data['summary'] = summary_node.get_text()
return data
5.outputer
这部分可以根据需要随便写,我这里是输出到本地文件中
class Outputer(object):
def collect(self, new_data):
with open('baidu.txt','a',encoding='utf-8') as f:
f.writelines(new_data['title']+':'+'\n')
f.writelines(new_data['summary']+'\n')
人生苦短,我用python,python写爬虫真的很方便