您的位置:首页 > 编程语言 > Python开发

python实现一个简单的爬虫

2015-05-03 23:48 766 查看
今天第一次写爬虫,感觉非常有趣!,中途也遇到了许多问题,所以写篇博客~

目标:爬取豆瓣编程类书籍中9分以上的

刚接触爬虫,说下我的认识(不一定准确^_^)

我们知道网页的呈现也是用编程语言写出来的,有源码,每个网页我们都可以查看它的源码,我的浏览器快捷键是Ctrl+U,

一般点击右键就可以看见查看源码。因为要爬取豆瓣的数据,那看看下图豆瓣图书页面的部分源码



它所对应的数据是这样的



那么我们知道了,网页上所能看见的每个数据在源码上都能找到,有的点击会跳转也是因为源码上链接着其他地方。

所以我们直接分析源码即可。

思路

其实很简单,我们想要爬取编程类9分以上的书籍。那么从编程类的首页开始爬取数据。

用正则表达式来过滤数据,取出9分以上的数据,关于正则表达式如果不熟悉可参考我的上篇文章^_^

然后我们在每个网页的最后可以发现跳转到下一个网页的链接,通过正则表达式过滤出这个链接即可。

保存这个链接作为下一次爬取的url。

我设置的结束条件是爬取当前页的9分以上书籍为0时跳出循环,为了简单起见。

看代码:

#!/usr/bin/env python
#coding:UTF-8

import urllib
import re

#得到一个网页的源码
def gethtml(url):
    #返回类似文件描述副,可进行读操作,read返回str
    page = urllib.urlopen(url)
    html = page.read()
    return html

#根据正则得到我们想要的数据
def getdata(obj):
    patt = r'(<dl>.+?>(9\.\d).+?</dl>)'
    #编译正则模块
    pattern = re.compile(patt, re.DOTALL)
    #findall查询此页面所有符合条件的数据,返回list
    m = pattern.findall(obj)
    Len = len(m)
    print 'len: %d' % Len
    if m is not None:
        for i in m:
            print i[1],
        print
    else:
        print 'not found'
    #如果此页面的9分数据为0,则认为没有9分数据
    if Len == 0:
        return False
    else:
        return True

#得到下一个页面
def getnextpage(obj):
    #我们要爬去的每个页面url前面都一样,所以只换后面的数据即可
    nextpage = 'http://www.douban.com/tag/%E7%BC%96%E7%A8%8B/book?start='
    #根据正则匹配后面的数据,然后连接到一起
    patt = '<span class="break">...</span>.+start=(\d+)'
    pattern = re.compile(patt, re.DOTALL)
    #下一个页面只有一个符合数据,所以用search
    m = pattern.search(obj)
    if m is not None:
        nextpage += m.group(1)
    else:
        print 'not found'
    return nextpage

if __name__ == '__main__':
    #起始页面
    html = gethtml("http://www.douban.com/tag/%E7%BC%96%E7%A8%8B/book")
    
    while 1:
        if getdata(html) == False:
            break
        ret = getnextpage(html)
        html = gethtml(ret)


注释写的比较详细。运行结果我输出了每个页面的书籍分数,len是此页面9分以上书籍的数量
运行结果:部分数据



遇到的问题:貌似都是正则表达式的问题 - - 。

1.

网页源码中每个书籍是这样的



可以看见我们要分析的数据中间包含中文,过滤中文首先必须要在开头加上

#coding:UTF-8,' . '符号在正则中可以过滤任何字符,\w只能是字母和数字

其次这段数据中包含了许多换行符号,在正则表达式中\s只能过滤空白字符,不能过滤回车。

我们需要在

#编译正则模块
    pattern = re.compile(patt, re.DOTALL)
里面设则,DOTALL的意思就是忽略回车符号

2.findall的问题

findall是正则匹配中一次匹配多个数据的函数

它返回的是一个list。

先来看看遇到的问题吧

在getdata中,我的需求是获取每本书的整个标签,也就是上面的图片,但是我还想顺便过滤出分数,就是把分数也作为一个分组可以查看。

类似m = search.( ),m.group(0),m.group(1)。

但是findall返回的是list,里面的数据是str。

和小伙伴讨论后又仔细了书,结论:

当正则表达式只有一个子组的时候(()是一个子组),findall( )返回子组匹配的字符串组成的列表,如果表达式有多个子组,返回的是一个元组的列表

元组中的每个元素都是一个子组的匹配内容,像这样的元组构成了返回列表中的元素

这也是为什么代码中我用的是m[1],因为返回的是一个元组呗!

顺便吐槽下python核心编程这本书,我目前发现的印刷错误至少十几处了!

而且findall( )这部分除了简单的介绍外没有给任何例子,结尾还说了句 “这些内容初次听到可能感到费解,但如果你看看各种例子,就会明白了”

它没给例子...

3.贪婪匹配

很重要,当时我只是简单的看了看,没太在意,结果实战就出错了

原本我的getdata正则是这样写的

def getdata(obj):
    patt = r'(<dl>.+>(9\.\d).+</dl>)'
怎么匹配也出不来

于是发现了贪婪匹配的问题

正则表达式默认是贪心匹配的,简单来说,如果正则表达式用到通配符(*, +, ?)等,它在从左到右匹配的时候会尽量匹配

最长的字符串。

看个例子:

#!/usr/bin/env python
#coding:UTF-8

import re

s = 'helloworld800-333-3333'
patt = '.+(\d+-\d+-\d+)'

m = re.search(patt, s)
print m.group(1)
我们想得出结果800-333-3333这个号码

结果却是



因为patt前面的'.+'默认是贪婪匹配的,它会匹配它能匹配尽量多的字符,所以我们后面的\d+只能匹配了一个0而已

解决办法是用非贪婪操作符号 ' ? '

那么'.+'就不会尽量读取多的字符

patt = '.+?(\d+-\d+-\d+)'
可以运行下试试,结果为800-333-3333

正则表达式问题的却比较多,一个学长建议我用BeautifulSoup来解析,会方便很多,感兴趣可以试试。

简单的入门,见笑了 ^_^
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: