python爬虫实战(一)


看了网上好多人写的爬虫,架构风格都不是很喜欢,前几天在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写爬虫真的很方便

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值