Python爬爬虫图片入门其实不难,就看你如何选择

51CTO旗下网站
我是如何零基础开始能写Python爬虫的
刚开始接触爬虫的时候,简直惊为天人,十几行代码,就可以将无数网页的信息全部获取下来,自动选取网页元素,自动整理成结构化的文件。利用这些数据,可以做很多领域的分析、市场调研,获得很多有价值的信息。这种技能不为我所用实在可惜,于是果断开始学习。
作者:佚名来源:| 10:17
刚开始接触爬虫的时候,简直惊为天人,十几行代码,就可以将无数网页的信息全部获取下来,自动选取网页元素,自动整理成结构化的文件。
利用这些数据,可以做很多领域的分析、市场调研,获得很多有价值的信息。这种技能不为我所用实在可惜,于是果断开始学习。
- ❶ -并非开始都是最容易的
刚开始对爬虫不是很了解,又没有任何的计算机、编程基础,确实有点懵逼。从哪里开始,哪些是最开始应该学的,哪些应该等到有一定基础之后再学,也没个清晰的概念。
因为是 Python 爬虫嘛,Python 就是必备的咯,那先从 Python 开始吧。于是看了一些教程和书籍,了解基本的数据结构,然后是列表、字典、元组,各种函数和控制语句(条件语句、循环语句)。
学了一段时间,才发现自己还没接触到真正的爬虫呢,而且纯理论学习很快就忘了,回去复习又太浪费时间,简直不要太绝望。把 Python 的基础知识过了一遍之后,我竟然还没装一个可以敲代码的IDE,想想就哭笑不得。
- ❷ -开始直接上手
转机出现在看过一篇爬虫的技术文章后,清晰的思路和通俗易懂的语言让我觉得,这才是我想学的爬虫。于是决定先配一个环境,试试看爬虫到底是怎么玩的。(当然你可以理解为这是浮躁,但确实每个小白都想去做直观、有反馈的事情)
因为怕出错,装了比较保险的 Anaconda,用自带的 Jupyter Notebook 作为IDE来写代码。看到很多人说因为配置环境出各种BUG,简直庆幸。很多时候打败你的,并不是事情本身,说的就是爬虫配置环境这事儿。
遇到的另一个问题是,Python 的爬虫可以用很多包或者框架来实现,应该选哪一种呢?我的原则就是是简单好用,写的代码少,对于一个小白来说,性能、效率什么的,统统被我 pass 了。于是开始接触 urllib、美丽汤(BeautifulSoup),因为听别人说很简单。
我上手的第一个案例是爬取豆瓣的电影,无数人都推荐把豆瓣作为新手上路的实例,因为页面简单且反爬虫不严。照着一些爬取豆瓣电影的入门级例子开始看,从这些例子里面,了解了一点点爬虫的基本原理:下载页面、解析页面、定位并抽取数据。
当然并没有去系统看 urllib 和 BeautifulSoup 了,我需要把眼前实例中的问题解决,比如下载、解析页面,基本都是固定的语句,直接用就行,我就先不去学习原理了。
用 urllib 下载和解析页面的固定句式
当然 BeautifulSoup 中的基本方法是不能忽略的,但也无非是 find、get_text() 之类,信息量很小。就这样,通过别人的思路和自己查找美丽汤的用法,完成了豆瓣电影的基本信息爬取。
用 BeautifulSoup 爬取豆瓣电影详情
- ❸ -爬虫渐入佳境
有了一些套路和形式,就会有目标,可以接着往下学了。还是豆瓣,自己去摸索爬取更多的信息,爬取多部电影,多个页面。这个时候就发现基础不足了,比如爬取多个元素、翻页、处理多种情况等涉及的语句控制,又比如提取内容时涉及到的字符串、列表、字典的处理,还远远不够。
再回去补充 Python 的基础知识,就很有针对性,而且能马上能用于解决问题,也就理解得更深刻。这样直到把豆瓣的TOP250图书和电影爬下来,基本算是了解了一个爬虫的基本过程了。
BeautifulSoup 还算不错,但需要花一些时间去了解一些网页的基本知识,否则一些元素的定位和选取还是会头疼。
后来认识到 xpath 之后相见恨晚,这才是入门必备利器啊,直接Chrome复制就可以了,指哪打哪。即便是要自己写 xpath,以w3school上几页的 xpath 教程,一个小时也可以搞定了。requests 貌似也比 urllib 更好用,但摸索总归是试错的过程,试错成本就是时间。
requests+xpath 爬取豆瓣TOP250图书信息
- ❹ -跟反爬虫杠上了
通过 requests+xpath,我可以去爬取很多网站网站了,后来自己练习了小猪的租房信息和当当的图书数据。爬拉勾的时候就发现问题了,首先是自己的请求根本不会返回信息,原来要将自己的爬虫伪装成浏览器,终于知道别人代码中那一坨 headers 信息是干啥的了😂。
在爬虫中添加 headers 信息,伪装成真实用户
接着是各种定位不到元素,然后知道了这是异步加载,数据根本不在网页源代码中,需要通过抓包来获取网页信息。于是在各种 JS、XHR的文件中 preview,寻找包含数据的链接。
当然知乎还好,本身加载的文件不多,找到了 json 文件直接获取对应的数据。(这里要安利一个chrome插件:jsonview,让小白轻松看懂 json 文件)
浏览器抓取 JavaScript 加载的数据
在这里就对反爬虫有了认识,当然这还是最基本的,更严格的IP限制、验证码、文字加密等等,可能还会遇到很多难题。但是目前的问题能够解决不就很好了吗,逐个击破才能更高效地学习。
比如后来在爬其他网站的时候就被封了IP,简单的可以通过 time.sleep() 控制爬取频率的方法解决,限制比较严格或者需要保证爬取速度,就要用代理IP来解决。
当然,后来也试了一下 Selenium,这个就真的是按照真实的用户浏览行为(点击、搜索、翻页)来实现爬虫,所以对于那些反爬虫特别厉害的网站,又没有办法解决,Selenium 是一个超级好用的东东,虽然速度稍微慢点。
- ❺ -尝试强大的 Scrapy 框架
有了 requests+xpath 和抓包大法,就可以做很多事情了,豆瓣各分类下的电影,58同城、知乎、拉勾这些网站基本都没问题。不过,当爬取的数据量级很大,而且需要灵活地处理各个模块的话,会显得很力不从心。
于是了解到强大的 Scrapy 框架,它不仅能便捷地构建 Request,还有强大的 Selector 能够方便地解析 Response,然而最让人惊喜的还是它超高的性能,可以将爬虫工程化、模块化。
Scrapy 框架的基本组件
学会 Scrapy,自己去尝试搭建了简单的爬虫框架,在做大规模数据爬去的时候能够结构化、工程化地思考大规模的爬取问题,这使我可以从爬虫工程的维度去思考问题。
当然 Scrapy 本身的 selector 、中间件、spider 等会比较难理解,还是建议结合具体的例子,参考别人的代码,去理解其中实现的过程,这样能够更好地理解。
用 Scrapy 爬取了大量租房信息
- ❻ -本地文件搞不动了,上数据库
爬回来大量的数据之后就发现,本地的文件存起来非常不方便,即便存下来了,打开大文件电脑会卡得很严重。怎么办呢?果断上数据库啊,于是开始入坑 MongoDB。结构化、非结构化的数据都能够存储,安装好 PyMongo,就可以方便地在 Python 中操作数据库了。
MongoDB 本身安装会比较麻烦,如果自己一个人去折腾,很有可能会陷入困境。刚开始安装的时候也是出现各种BUG,幸得大神小X指点,解决了很多问题。
当然对于爬虫这一块,并不需要多么高深的数据库技术,主要是数据的入库和提取,顺带掌握了基本的插入、删除等操作。总之,能够满足高效地提取爬下来的数据就OK了。
爬取拉勾招聘数据并用 MongoDB 存储
- ❼ -传说中的分布式爬虫
这个时候,基本上很大一部分的网页都能爬了,瓶颈就集中到爬取大规模数据的效率。因为学了 Scrapy,于是自然地接触到一个很厉害的名字:分布式爬虫。
分布式这个东西,一听不明觉厉,感觉很恐怖,但其实就是利用多线程的原理让多个爬虫同时工作,除了前面学过的 Scrapy 和 MongoDB,好像还需要了解 Redis。
Scrapy 用于做基本的页面爬取,MongoDB 用于存储爬取的数据,Redis 则用来存储要爬取的网页队列,也就是任务队列。
分布式这东西看起来很吓人,但其实分解开来,循序渐进地学习,也不过如此。
分布式爬58同城:定义项目内容部分
零基础学习爬虫,坑确实比较多,总结如下:
1.环境配置,各种安装包、环境变量,对小白太不友好;
2.缺少合理的学习路径,上来 Python、HTML 各种学,极其容易放弃;
3.Python有很多包、框架可以选择,但小白不知道哪个更友好;
4.遇到问题甚至不知道如何描述,更不用说去寻找解决办法;
5.网上的资料非常零散,而且对小白不友好,很多看起来云里雾里;
6.有些东西看似懂了,但结果自己写代码还是很困难;
所以跟我一样,很多人爬坑最大的体会是:尽量不要系统地去啃一些东西,找一个实际的项目(从豆瓣这种简单的入手),直接开始就好。【编辑推荐】【责任编辑: TEL:(010)】
大家都在看猜你喜欢
关注头条热点头条热点
24H热文一周话题本月最赞
讲师:107285人学习过
讲师:35696人学习过
讲师:44091人学习过
CTO专属活动
精选博文论坛热帖下载排行
本书通过大量实例代码,以ECMA-262版本3为基础,结合JavaScript 1.5和JavaScript 5.5,由浅入深、循序渐进地介绍了JavaScript知识要点与编...
订阅51CTO邮刊当前位置: >>
python爬虫入门教程
Python 爬虫入门三之 Urllib 库的基本使用Python 崔庆才 5 个月前 (02-12) 13660℃ 13 评论 那么接下来,小伙伴们就一起和我真正迈向我们的爬虫之路吧。1.分分钟扒一个网页下来怎样扒网页呢?其实就是根据 URL 来获取它的网页信息,虽然我们在浏览器中 看到的是一幅幅优美的画面, 但是其实是由浏览器解释才呈现出来的,实质它是 一段 HTML 代码,加 JS、CSS,如果把网页比作一个人,那么 HTML 便是他的 骨架, JS 便是他的肌肉, CSS 便是它的衣服。 所以最重要的部分是存在于 HTML 中的,下面我们就写个例子来扒一个网页下来。1import urllib2 2 3response = urllib2.urlopen(&http://www.baidu.com&) 4print response.read()是的你没看错,真正的程序就两行,把它保存成 demo.py,进入该文件的目录, 执行如下命令查看运行结果,感受一下。1python demo.py第 1 页 共 191 页 看,这个网页的源码已经被我们扒下来了,是不是很酸爽?2.分析扒网页的方法那么我们来分析这两行代码,第一行1response = urllib2.urlopen(&http://www.baidu.com&)首先我们调用的是 urllib2 库里面的 urlopen 方法,传入一个 URL,这个网址是百 度首页,协议是 HTTP 协议,当然你也可以把 HTTP 换做 FTP,FILE,HTTPS 等 等,只是代表了一种访问控制协议,urlopen 一般接受三个参数,它的参数如下:第 2 页 共 191 页 1urlopen(url, data, timeout)第一个参数 url 即为 URL,第二个参数 data 是访问 URL 时要传送的数据,第三 个 timeout 是设置超时时间。 第二三个参数是可以不传送的,data 默认为空 None,timeout 默认 为 socket._GLOBAL_DEFAULT_TIMEOUT 第一个参数 URL 是必须要传送的,在这个例子里面我们传送了百度的 URL,执 行 urlopen 方法之后,返回一个 response 对象,返回信息便保存在这里面。1print response.read()response 对象有一个 read 方法,可以返回获取到的网页内容。 如果不加 read 直接打印会是什么?答案如下:1&addinfourl at 376 whose fp = &socket._fileobject object at 0x7f&&直接打印出了该对象的描述,所以记得一定要加 read 方法,否则它不出来内容 可就不怪我咯!3.构造 Requset其实上面的 urlopen 参数可以传入一个 request 请求,它其实就是一个 Request 类的 实例,构造时需要传入 Url,Data 等等的内容。比如上面的两行代码,我们可以这 么改写第 3 页 共 191 页 1import urllib2 2 3request = urllib2.Request(&http://www.baidu.com&) 4response = urllib2.urlopen(request) 5print response.read()运行结果是完全一样的,只不过中间多了一个 request 对象,推荐大家这么写, 因为在构建请求时还需要加入好多内容,通过构建一个 request,服务器响应请求 得到应答,这样显得逻辑上清晰明确。4.POST 和 GET 数据传送上面的程序演示了最基本的网页抓取,不过,现在大多数网站都是动态网页,需 要你动态地传递参数给它,它做出对应的响应。所以,在访问时,我们需要传递 数据给它。最常见的情况是什么?对了,就是登录注册的时候呀。 把数据用户名和密码传送到一个 URL,然后你得到服务器处理之后的响应,这 个该怎么办?下面让我来为小伙伴们揭晓吧! 数据传送分为 POST 和 GET 两种方式,两种方式有什么区别呢? 最重要的区别是 GET 方式是直接以链接形式访问,链接中包含了所有的参数, 当然如果包含了密码的话是一种不安全的选择, 不过你可以直观地看到自己提交 了什么内容。POST 则不会在网址上显示所有的参数,不过如果你想直接查看提 交了什么就不太方便了,大家可以酌情选择。 POST 方式: 上面我们说了 data 参数是干嘛的?对了,它就是用在这里的,我们传送的数据 就是这个参数 data,下面演示一下 POST 方式。1import urllib第 4 页 共 191 页 2import urllib2 3 4values = {&username&:&&,&password&:&XXXX&} 5data = urllib.urlencode(values) 6url = &https://passport.csdn.net/account/login?from=http://my.csdn.net/my/mycsdn& 7request = urllib2.Request(url,data) 8response = urllib2.urlopen(request) 9print response.read()我们引入了 urllib 库, 现在我们模拟登陆 CSDN, 当然上述代码可能登陆不进去, 因为还要做一些设置头部 header 的工作,或者还有一些参数没有设置全,还没 有提及到在此就不写上去了, 在此只是说明登录的原理。 我们需要定义一个字典, 名字为 values, 参数我设置了 username 和 password, 下面利用 urllib 的 urlencode 方法将字典编码,命名为 data,构建 request 时传入两个参数,url 和 data,运行 程序,即可实现登陆,返回的便是登陆后呈现的页面内容。当然你可以自己搭建 一个服务器来测试一下。 注意上面字典的定义方式还有一种,下面的写法是等价的1import urllib 2import urllib2 3 4values = {} 5values['username'] = && 6values['password'] = &XXXX& 7data = urllib.urlencode(values)第 5 页 共 191 页 8url = &http://passport.csdn.net/account/login?from=http://my.csdn.net/my/mycsdn& 9request = urllib2.Request(url,data) 10response = urllib2.urlopen(request) 11print response.read()以上方法便实现了 POST 方式的传送 GET 方式: 至于 GET 方式我们可以直接把参数写到网址上面,直接构建一个带参数的 URL 出来即可。1import urllib 2import urllib2 3 4values={} 5values['username'] = && 6values['password']=&XXXX& 7data = urllib.urlencode(values) 8url = &http://passport.csdn.net/account/login& 9geturl = url + &?&+data 10request = urllib2.Request(geturl) 11response = urllib2.urlopen(request) 12print response.read()你可以 print geturl,打印输出一下 url,发现其实就是原来的 url 加?然后加编码 后的参数第 6 页 共 191 页 1http://passport.csdn.net/account/login?username=%40qq.com&password=XXXX和我们平常 GET 访问方式一模一样,这样就实现了数据的 GET 方式传送。 本节讲解了一些基本使用,可以抓取到一些基本的网页信息,小伙伴们加油! 转载请注明:静觅 ?Python 爬虫入门三之 Urllib 库的基本使用喜欢 (75) or 分享 (Python 爬虫入门四之 Urllib 库的高级用法Python 崔庆才 5 个月前 (02-12) 10284℃ 6 评论1.设置 Headers有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点 根本不会响应, 所以为了完全模拟浏览器的工作, 我们需要设置一些 Headers 的 属性。 首先,打开我们的浏览器,调试浏览器 F12,我用的是 Chrome,打开网络监听, 示意如下,比如知乎,点登录之后,我们会发现登陆之后界面都变化了,出现一 个新的界面, 实质上这个页面包含了许许多多的内容,这些内容也不是一次性就 加载完成的,实质上是执行了好多次请求,一般是首先请求 HTML 文件,然后 加载 JS,CSS 等等,经过多次请求之后,网页的骨架和肌肉全了,整个网页的 效果也就出来了。第 7 页 共 191 页 拆分这些请求,我们只看一第一个请求,你可以看到,有个 Request URL,还有 headers,下面便是 response,图片显示得不全,小伙伴们可以亲身实验一下。那 么这个头中包含了许许多多是信息,有文件编码啦,压缩方式啦,请求的 agent 啦等等。 其中,agent 就是请求的身份,如果没有写入请求身份,那么服务器不一定会响 应,所以可以在 headers 中设置 agent,例如下面的例子,这个例子只是说明了怎 样设置的 headers,小伙伴们看一下设置格式就好。1import urllib 2import urllib2 3 4url = 'http://www.server.com/login'第 8 页 共 191 页 5user_agent = 'Mozilla/4.0 ( MSIE 5.5; Windows NT)' 6values = {'username' : 'cqc', 'password' : 'XXXX' } 7headers = { 'User-Agent' : user_agent } 8data = urllib.urlencode(values) 9request = urllib2.Request(url, data, headers) 10response = urllib2.urlopen(request) 11page = response.read()这样, 我们设置了一个 headers, 在构建 request 时传入, 在请求时, 就加入了 headers 传送,服务器若识别了是浏览器发来的请求,就会得到响应。 另外,我们还有对付”反盗链”的方式,对付防盗链,服务器会识别 headers 中的 referer 是不是它自己, 如果不是, 有的服务器不会响应, 所以我们还可以在 headers 中加入 referer 例如我们可以构建下面的 headers1headers = { 'User-Agent' : 'Mozilla/4.0 ( MSIE 5.5; Windows NT)' , 2 'Referer':'http://www.zhihu.com/articles' }同上面的方法,在传送请求时把 headers 传入 Request 参数里,这样就能应付防 盗链了。 另外 headers 的一些属性,下面的需要特别注意一下:User-Agent : 有些服务器或 Proxy 会通过该值来判断是否是浏览器发出 的请求 Content-Type : 在使用 REST 接口时,服务器会检查该值,用来确定 HTTP Body 中的内容该怎样解析。 application/xml : 在 XML RPC,如 RESTful/SOAP 调用时使用 application/json : 在 JSON RPC 调用时使用 application/x-www-form-urlencoded : 浏览器提交 Web 表单时使用 在使用服务器提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误 会导致服务器拒绝服务第 9 页 共 191 页 其他的有必要的可以审查浏览器的 headers 内容, 在构建时写入同样的数据即可。2. Proxy(代理)的设置urllib2 默认会使用环境变量 http_proxy 来设置 HTTP Proxy。假如一个网站它会 检测某一段时间某个 IP 的访问次数,如果访问次数过多,它会禁止你的访问。 所以你可以设置一些代理服务器来帮助你做工作,每隔一段时间换一个代理,网 站君都不知道是谁在捣鬼了,这酸爽! 下面一段代码说明了代理的设置用法1import urllib2 2enable_proxy = True 3proxy_handler = urllib2.ProxyHandler({&http& : 'http://some-proxy.com:8080'}) 4null_proxy_handler = urllib2.ProxyHandler({}) 5if enable_proxy: 6 opener = urllib2.build_opener(proxy_handler)7else: 8 opener = urllib2.build_opener(null_proxy_handler)9urllib2.install_opener(opener)3. Timeout 设置上一节已经说过 urlopen 方法了,第三个参数就是 timeout 的设置,可以设置等 待多久超时,为了解决一些网站实在响应过慢而造成的影响。 例如下面的代码,如果第二个参数 data 为空那么要特别指定是 timeout 是多少, 写 明形参,如果 data 已经传入,则不必声明。第 10 页 共 191 页 1import urllib2 2response = urllib2.urlopen('http://www.baidu.com', timeout=10)1import urllib2 2response = urllib2.urlopen('http://www.baidu.com',data, 10)4.使用 HTTP 的 PUT 和 DELETE 方法http 协议有六种请求方法,get,head,put,delete,post,options,我们有时候需要用到 PUT 方式或者 DELETE 方式请求。PUT:这个方法比较少见。HTML 表单也不支持这个。本质上来讲, PUT 和 POST 极为相似,都是向服务器发送数据,但它们之间有一个重要区别,PUT 通常指定了资源的存放位置,而 POST 则没有,POST 的数据存放位置由服务 器自己决定。 DELETE:删除某一个资源。基本上这个也很少见,不过还是有一些地方比如 amazon 的 S3 云服务里面就用的这个方法来删除资源。如果要使用 HTTP PUT 和 DELETE , 只能使用比较低层的 httplib 库。 虽然如 此, 我们还是能通过下面的方式, 使 urllib2 能够发出 PUT 或 DELETE 的请求, 不过用的次数的确是少,在这里提一下。1import urllib2 2request = urllib2.Request(uri, data=data) 3request.get_method = lambda: 'PUT' # or 'DELETE'第 11 页 共 191 页 4response = urllib2.urlopen(request)5.使用 DebugLog可以通过下面的方法把 Debug Log 打开,这样收发包的内容就会在屏幕上打印 出来,方便调试,这个也不太常用,仅提一下1import urllib2 2httpHandler = urllib2.HTTPHandler(debuglevel=1) 3httpsHandler = urllib2.HTTPSHandler(debuglevel=1) 4opener = urllib2.build_opener(httpHandler, httpsHandler) 5urllib2.install_opener(opener) 6response = urllib2.urlopen('http://www.baidu.com')以上便是一部分高级特性,前三个是重要内容,在后面,还有 cookies 的设置还 有异常的处理,小伙伴们加油!Python 爬虫入门五之 URLError 异常处理Python 崔庆才 5 个月前 (02-13) 6387℃ 3 评论 大家好,本节在这里主要说的是 URLError 还有 HTTPError,以及对它们的一些 处理。1.URLError首先解释下 URLError 可能产生的原因:? 网络无连接,即本机无法上网 ? 连接不到特定的服务器 ? 服务器不存在第 12 页 共 191 页 在代码中,我们需要用 try-except 语句来包围并捕获相应的异常。下面是一个例 子,先感受下它的风骚1import urllib2 2 3requset = urllib2.Request('http://www.xxxxx.com') 4try: 5 urllib2.urlopen(requset)6except urllib2.URLError, e: 7 print e.reason我们利用了 urlopen 方法访问了一个不存在的网址,运行结果如下:1[Errno 11004] getaddrinfo failed它说明了错误代号是 11004,错误原因是 getaddrinfo failed 2.HTTPError HTTPError 是 URLError 的子类, 在你利用 urlopen 方法发出一个请求时, 服务器 上都会对应一个应答对象 response,其中它包含一个数字”状态码”。举个例子, 假如 response 是一个”重定向”,需定位到别的地址获取文档,urllib2 将对此进行 处理。 其他不能处理的,urlopen 会产生一个 HTTPError,对应相应的状态吗,HTTP 状 态码表示 HTTP 协议所返回的响应的状态。下面将状态码归结如下:100:继续 客户端应当继续发送请求。客户端应当继续发送请求的剩余部 分,或者如果请求已经完成,忽略这个响应。第 13 页 共 191 页 101: 转换协议 在发送完这个响应最后的空行后,服务器将会切换到在 Upgrade 消息头中定义的那些协议。 只有在切换新的协议更有好处的时候才 应该采取类似措施。 102:继续处理 续执行。 200:请求成功 由 WebDAV(RFC 2518)扩展的状态码,代表处理将被继 处理方式:获得响应的内容,进行处理201:请求完成,结果是创建了新资源。新创建资源的 URI 可在响应的实体 中得到 处理方式:爬虫中不会遇到 202:请求被接受,但处理尚未完成 处理方式:阻塞等待204:服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户 代理,则无须为此更新自身的文档视图。 处理方式:丢弃 300:该状态码不被 HTTP/1.0 的应用程序直接使用, 只是作为 3XX 类型回 应的默认解释。存在多个可用的被请求资源。 处理方式:若程序中能够 处理,则进行进一步处理,如果程序中不能处理,则丢弃 301:请求到的资源都会分配一个永久的 URL,这样就可以在将来通过该 URL 来访问此资源 处理方式:重定向到分配的 URL 302:请求到的资源在一个不同的 URL 处临时保存 临时的 URL 304:请求的资源未更新 400:非法请求 401:未授权 403:禁止 404:没有找到 处理方式:丢弃 处理方式:重定向到处理方式:丢弃 处理方式:丢弃 处理方式:丢弃 处理方式:丢弃500:服务器内部错误 服务器遇到了一个未曾预料的状况,导致了它无法 完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误 时出现。 501:服务器无法识别 服务器不支持当前请求所需要的某个功能。当服务 器无法识别请求的方法,并且无法支持其对任何资源的请求。 502:错误网关 作为网关或者代理工作的服务器尝试执行请求时,从上游 服务器接收到无效的响应。 503:服务出错 由于临时的服务器维护或者过载,服务器当前无法处理请 求。这个状况是临时的,并且将在一段时间以后恢复。HTTPError 实例产生后会有一个 code 属性,这就是是服务器发送的相关错误号。 因为 urllib2 可以为你处理重定向, 也就是 3 开头的代号可以被处理, 并且 100-299 范围的号码指示成功,所以你只能看到 400-599 的错误号码。 下面我们写一个例子来感受一下,捕获的异常是 HTTPError,它会带有一个 code 属性,就是错误代号,另外我们又打印了 reason 属性,这是它的父类 URLError 的属性。第 14 页 共 191 页 1import urllib2 2 3req = urllib2.Request('http://blog.csdn.net/cqcre') 4try: 5 urllib2.urlopen(req)6except urllib2.HTTPError, e: 7 8 print e.code print e.reason运行结果如下1403 2Forbidden错误代号是 403,错误原因是 Forbidden,说明服务器禁止访问。 我们知道,HTTPError 的父类是 URLError,根据编程经验,父类的异常应当写到 子类异常的后面,如果子类捕获不到,那么可以捕获父类的异常,所以上述的代 码可以这么改写1import urllib2 2第 15 页 共 191 页 3req = urllib2.Request('http://blog.csdn.net/cqcre') 4try: 5 urllib2.urlopen(req)6except urllib2.HTTPError, e: 7 print e.code8except urllib2.URLError, e: 9 print e.reason10else: 11 print &OK&如果捕获到了 HTTPError,则输出 code,不会再处理 URLError 异常。如果发生 的不是 HTTPError,则会去捕获 URLError 异常,输出错误原因。 另外还可以加入 hasattr 属性提前对属性进行判断,代码改写如下1import urllib2 2 3req = urllib2.Request('http://blog.csdn.net/cqcre') 4try: 5 urllib2.urlopen(req)6except urllib2.URLError, e: 7 8 9 if hasattr(e,&code&): print e.code if hasattr(e,&reason&):第 16 页 共 191 页 10 11else: 12print e.reasonprint &OK&首先对异常的属性进行判断,以免出现属性输出报错的现象。 以上,就是对 URLError 和 HTTPError 的相关介绍,以及相应的错误处理办法, 小伙伴们加油! 转载请注明:静觅 ?Python 爬虫入门五之 URLError 异常处理喜欢 (40) or 分享 (1)Python 爬虫入门六之 Cookie 的使用Python 崔庆才 5 个月前 (02-14) 8572℃ 18 评论 大家好哈, 上一节我们研究了一下爬虫的异常处理问题,那么接下来我们一起来 看一下 Cookie 的使用。 为什么要使用 Cookie 呢? Cookie,指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终 端上的数据(通常经过加密) 比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面 内容是不允许的。那么我们可以利用 Urllib2 库保存我们登录的 Cookie,然后再 抓取其他页面就达到目的了。 在此之前呢,我们必须先介绍一个 opener 的概念。1.Opener当你获取一个 URL 你使用一个 opener(一个 urllib2.OpenerDirector 的实例)。 在前 面,我们都是使用的默认的 opener,也就是 urlopen。它是一个特殊的 opener, 可以理解成 opener 的一个特殊实例,传入的参数仅仅是 url,data,timeout。 如果我们需要用到 Cookie,只用这个 opener 是不能达到目的的,所以我们需要 创建更一般的 opener 来实现对 Cookie 的设置。2.Cookielib第 17 页 共 191 页 cookielib 模块的主要作用是提供可存储 cookie 的对象,以便于与 urllib2 模块配 合使用来访问 Internet 资源。Cookielib 模块非常强大,我们可以利用本模块的 CookieJar 类的对象来捕获 cookie 并在后续连接请求时重新发送,比如可以实现 模拟登录功能。 该模块主要的对象有 CookieJar、 FileCookieJar、 MozillaCookieJar、 LWPCookieJar。 它们的关系:CookieJar ―-派生―-&FileCookieJar ―-派生―C&MozillaCookieJar 和 LWPCookieJar 1)获取 Cookie 保存到变量 首先,我们先利用 CookieJar 对象实现获取 cookie 的功能,存储到变量中,先来 感受一下1import urllib2 2import cookielib 3#声明一个 CookieJar 对象实例来保存 cookie 4cookie = cookielib.CookieJar() 5#利用 urllib2 库的 HTTPCookieProcessor 对象来创建 cookie 处理器 6handler=urllib2.HTTPCookieProcessor(cookie) 7#通过 handler 来构建 opener 8opener = urllib2.build_opener(handler) 9#此处的 open 方法同 urllib2 的 urlopen 方法,也可以传入 request 10response = opener.open('http://www.baidu.com') 11for item in cookie: 12 13 print 'Name = '+item.name print 'Value = '+item.value第 18 页 共 191 页 我们使用以上方法将 cookie 保存到变量中,然后打印出了 cookie 中的值,运行 结果如下1Name = BAIDUID 2Value = B07B663BF659C02AAE65B4C:FG=1 3Name = BAIDUPSID 4Value = B07B663BF659C02AAE65B4C 5Name = H_PS_PSSID 6Value = _ 7Name = BDSVRTM 8Value = 0 9Name = BD_HOME 10Value = 02)保存 Cookie 到文件 在上面的方法中,我们将 cookie 保存到了 cookie 这个变量中,如果我们想将 cookie 保存到文件中该怎么做呢?这时,我们就要用到 FileCookieJar 这个对象了,在这里我们使用它的子类 MozillaCookieJar 来实现 Cookie 的保存1import cookielib 2import urllib2 3第 19 页 共 191 页 4#设置保存 cookie 的文件,同级目录下的 cookie.txt 5filename = 'cookie.txt' 6#声明一个 MozillaCookieJar 对象实例来保存 cookie,之后写入文件 7cookie = cookielib.MozillaCookieJar(filename) 8#利用 urllib2 库的 HTTPCookieProcessor 对象来创建 cookie 处理器 9handler = urllib2.HTTPCookieProcessor(cookie) 10#通过 handler 来构建 opener 11opener = urllib2.build_opener(handler) 12#创建一个请求,原理同 urllib2 的 urlopen 13response = opener.open(&http://www.baidu.com&) 14#保存 cookie 到文件 15cookie.save(ignore_discard=True, ignore_expires=True)关于最后 save 方法的两个参数在此说明一下: 官方解释如下:ignore_discard: save even cookies set to be discarded. ignore_expires: save even cookies that have expiredThe file is overwritten if it already exists由此可见,ignore_discard 的意思是即使 cookies 将被丢弃也将它保存下来, ignore_expires 的意思是如果在该文件中 cookies 已经存在,则覆盖原文件写入, 在这里, 我们将这两个全部设置为 True。 运行之后, cookies 将被保存到 cookie.txt 文件中,我们查看一下内容,附图如下第 20 页 共 191 页 3)从文件中获取 Cookie 并访问 那么我们已经做到把 Cookie 保存到文件中了,如果以后想使用,可以利用下面 的方法来读取 cookie 并访问网站,感受一下1import cookielib 2import urllib2 3 4#创建 MozillaCookieJar 实例对象 5cookie = cookielib.MozillaCookieJar() 6#从文件中读取 cookie 内容到变量 7cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True) 8#创建请求的 request 9req = urllib2.Request(&http://www.baidu.com&) 10#利用 urllib2 的 build_opener 方法创建一个 opener第 21 页 共 191 页 11opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) 12response = opener.open(req) 13print response.read()设想,如果我们的 cookie.txt 文件中保存的是某个人登录百度的 cookie,那么我 们提取出这个 cookie 文件内容,就可以用以上方法模拟这个人的账号登录百度。 4)利用 cookie 模拟网站登录 下面我们以我们学校的教育系统为例,利用 cookie 实现模拟登录,并将 cookie 信息保存到文本文件中,来感受一下 cookie 大法吧! 注意:密码我改了啊,别偷偷登录本宫的选课系统 o(s □t )o1import urllib 2import urllib2 3import cookielib 4 5filename = 'cookie.txt' 6#声明一个 MozillaCookieJar 对象实例来保存 cookie,之后写入文件 7cookie = cookielib.MozillaCookieJar(filename) 8opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) 9postdata = urllib.urlencode({ 10 11 12 }) 'stuid':'', 'pwd':';第 22 页 共 191 页 13#登录教务系统的 URL 14loginUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bks_login2.login' 15#模拟登录,并把 cookie 保存到变量 16result = opener.open(loginUrl,postdata) 17#保存 cookie 到 cookie.txt 中 18cookie.save(ignore_discard=True, ignore_expires=True) 19#利用 cookie 请求访问另一个网址,此网址是成绩查询网址 20gradeUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bkscjcx.curscopre' 21#请求访问成绩查询网址 22result = opener.open(gradeUrl) 23print result.read()以上程序的原理如下 创建一个带有 cookie 的 opener,在访问登录的 URL 时,将登录后的 cookie 保存 下来,然后利用这个 cookie 来访问其他网址。 如登录之后才能查看的成绩查询呀,本学期课表呀等等网址,模拟登录就这么实 现啦,是不是很酷炫? 好,小伙伴们要加油哦!我们现在可以顺利获取网站信息了,接下来就是把网站 里面有效内容提取出来,下一节我们去会会正则表达式! 转载请注明:静觅 ?Python 爬虫入门六之 Cookie 的使用第 23 页 共 191 页 Python 爬虫入门七之正则表达式Python 崔庆才 5 个月前 (02-15) 9787℃ 11 评论 在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码 夹杂文字我们怎样把它提取出来整理呢?下面就开始介绍一个十分强大的工具, 正则表达式!1.了解正则表达式正则表达式是对字符串操作的一种逻辑公式, 就是用事先定义好的一些特定 字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符 串”用来表达对字符串的一种过滤逻辑。正则表达式是用来匹配字符串非常强大的工具, 在其他编程语言中同样有正则表 达式的概念,Python 同样不例外,利用了正则表达式,我们想要从返回的页面内 容提取出我们想要的内容就易如反掌了。正则表达式的大致匹配过程是: 1.依次拿出表达式和文本中的字符比较, 2.如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配 失败。 3.如果表达式中有量词或边界,这个过程会稍微有一些不同。2.正则表达式的语法规则下面是 Python 中正则表达式的一些匹配规则,图片资料来自 CSDN第 24 页 共 191 页 第 25 页 共 191 页 3.正则表达式相关注解(1)数量词的贪婪模式与非贪婪模式 正则表达式通常用于在文本中查找匹配的字符串。 Python 里数量词默认是贪婪的 (在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪 的则相反,总是尝试匹配尽可能少的字符。例如:正则表达式”ab*”如果用于查 找”abbbc”,将找到”abbb”。而如果使用非贪婪的数量词”ab*?”,将找到”a”。 注:我们一般使用非贪婪模式来提取。 (2)反斜杠问题 与大多数编程语言相同,正则表达式里使用”\”作为转义字符,这就可能造成反斜 杠困扰。假如你需要匹配文本中的字符”\”,那么使用编程语言表示的正则表达 式里将需要 4 个反斜杠”\\\\”: 前两个和后两个分别用于在编程语言里转义成反斜 杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。 Python 里的原生字符串很好地解决了这个问题, 这个例子中的正则表达式可以使 用 r”\\”表示。同样,匹配一个数字的”\\d”可以写成 r”\d”。有了原生字符串,妈 妈也不用担心是不是漏写了反斜杠,写出来的表达式也更直观勒。4.Python Re 模块Python 自带了 re 模块,它提供了对正则表达式的支持。主要用到的方法列举如 下1#返回 pattern 对象 2re.compile(string[,flag]) 3#以下为匹配所用函数 4re.match(pattern, string[, flags]) 5re.search(pattern, string[, flags])第 26 页 共 191 页 6re.split(pattern, string[, maxsplit]) 7re.findall(pattern, string[, flags]) 8re.finditer(pattern, string[, flags]) 9re.sub(pattern, repl, string[, count]) 10re.subn(pattern, repl, string[, count])在介绍这几个方法之前,我们先来介绍一下 pattern 的概念,pattern 可以理解为 一个匹配模式,那么我们怎么获得这个匹配模式呢?很简单,我们需要利用 re.compile 方法就可以。例如1pattern = re.compile(r'hello')在参数中我们传入了原生字符串对象,通过 compile 方法编译生成一个 pattern 对象,然后我们利用这个对象来进行进一步的匹配。 另外大家可能注意到了另一个参数 flags,在这里解释一下这个参数的含义: 参数 flag 是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如 re.I | re.M。 可选值有:1? re.I(全拼:IGNORECASE): 忽略大小写(括号内是完整写法,下同) 2? re.M(全拼:MULTILINE): 多行模式,改变'^'和'$'的行为(参见上图) 3? re.S(全拼:DOTALL): 点任意匹配模式,改变'.'的行为 4? re.L(全拼:LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定 5? re.U(全拼:UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于 unicode 定义的字符属性第 27 页 共 191 页 6? re.X(全拼:VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入 注释。在刚才所说的另外几个方法例如 re.match 里我们就需要用到这个 pattern 了,下 面我们一一介绍。注:以下七个方法中的 flags 同样是代表匹配模式的意思,如果在 pattern 生成时已经指明了 flags,那么在下面的方法中就不需要传入这个参数了。(1)re.match(pattern, string[, flags]) 这个方法将会从 string(我们要匹配的字符串)的开头开始,尝试匹配 pattern, 一直向后匹配,如果遇到无法匹配的字符,立即返回 None,如果匹配未结束已 经到达 string 的末尾, 也会返回 None。 两个结果均表示匹配失败, 否则匹配 pattern 成功,同时匹配终止,不再对 string 向后匹配。下面我们通过一个例子理解一下1__author__ = 'CQC' 2# -*- coding: utf-8 -*3 4#导入 re 模块 5import re 6 7# 将正则表达式编译成 Pattern 对象,注意 hello 前面的 r 的意思是“原生字符串” 8pattern = re.compile(r'hello') 9 10# 使用 re.match 匹配文本,获得匹配结果,无法匹配时将返回 None 11result1 = re.match(pattern,'hello') 12result2 = re.match(pattern,'helloo CQC!')第 28 页 共 191 页 13result3 = re.match(pattern,'helo CQC!') 14result4 = re.match(pattern,'hello CQC!') 15 16#如果 1 匹配成功 17if result1: 18 19 # 使用 Match 获得分组信息 print result1.group()20else: 21 22 23 24#如果 2 匹配成功 25if result2: 26 27 # 使用 Match 获得分组信息 print result2.group() print '1 匹配失败!'28else: 29 30 31 32#如果 3 匹配成功 33if result3: 34 # 使用 Match 获得分组信息 print '2 匹配失败!'第 29 页 共 191 页 35print result3.group()36else: 37 38 39#如果 4 匹配成功 40if result4: 41 42 # 使用 Match 获得分组信息 print result4.group() print '3 匹配失败!'43else: 44 print '4 匹配失败!'运行结果1hello 2hello 33 匹配失败! 4hello匹配分析 1.第一个匹配,pattern 正则表达式为’hello’,我们匹配的目标字符串 string 也为 hello,从头至尾完全匹配,匹配成功。 2.第二个匹配,string 为 helloo CQC,从 string 头开始匹配 pattern 完全可以匹配, pattern 匹配结束,同时匹配终止,后面的 o CQC 不再匹配,返回匹配成功的信 息。 3.第三个匹配,string 为 helo CQC,从 string 头开始匹配 pattern,发现到 ‘o’ 时 无法完成匹配,匹配终止,返回 None第 30 页 共 191 页 4.第四个匹配,同第二个匹配原理,即使遇到了空格符也不会受影响。 我们还看到最后打印出了 result.group(),这个是什么意思呢?下面我们说一下关 于 match 对象的的属性和方法 Match 对象是一次匹配的结果, 包含了很多关于此次匹配的信息, 可以使用 Match 提供的可读属性或方法来获取这些信息。属性: 1.string: 匹配时使用的文本。 2.re: 匹配时使用的 Pattern 对象。 3.pos: 文本中正则表达式开始搜索的索引。值与 Pattern.match()和 Pattern.seach()方法的同名参数相同。 4.endpos: 文本中正则表达式结束搜索的索引。值与 Pattern.match()和 Pattern.seach()方法的同名参数相同。 5.lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的 分组,将为 None。 6.lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者 没有被捕获的分组,将为 None。 方法: 1.group([group1, ?]): 获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。 group1 可以使用编号也可以使用别名;编号 0 代表整个匹配的子串;不填 写参数时,返回 group(0);没有截获字符串的组返回 None;截获了多次的 组返回最后一次截获的子串。 2.groups([default]): 以元组形式返回全部分组截获的字符串。相当于调用 group(1,2,?last)。 default 表示没有截获字符串的组以这个值替代,默认为 None。 3.groupdict([default]): 返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的 组不包含在内。default 含义同上。 4.start([group]): 返回指定的组截获的子串在 string 中的起始索引(子串第一个字符的索 引)。group 默认值为 0。 5.end([group]): 返回指定的组截获的子串在 string 中的结束索引(子串最后一个字符的索 引+1)。group 默认值为 0。 6.span([group]): 返回(start(group), end(group))。 7.expand(template): 将匹配到的分组代入 template 中然后返回。 template 中可以使用\id 或\g、 \g 引用分组,但不能使用编号 0。\id 与\g 是等价的;但\10 将被认为是第 10 个分组,如果你想表达\1 之后是字符’0’,只能使用\g0。下面我们用一个例子来体会一下第 31 页 共 191 页 1# -*- coding: utf-8 -*2#一个简单的 match 实例 3 4import re 5# 匹配如下内容:单词+空格+单词+任意字符 6m = re.match(r'(\w+) (\w+)(?P.*)', 'hello world!') 7 8print &m.string:&, m.string 9print &m.re:&, m.re 10print &m.pos:&, m.pos 11print &m.endpos:&, m.endpos 12print &m.lastindex:&, m.lastindex 13print &m.lastgroup:&, m.lastgroup 14print &m.group():&, m.group() 15print &m.group(1,2):&, m.group(1, 2) 16print &m.groups():&, m.groups() 17print &m.groupdict():&, m.groupdict() 18print &m.start(2):&, m.start(2) 19print &m.end(2):&, m.end(2) 20print &m.span(2):&, m.span(2)第 32 页 共 191 页 21print r&m.expand(r'\g \g\g'):&, m.expand(r'\2 \1\3') 22### output ### 23# m.string: hello world! 24# m.re: 25# m.pos: 0 26# m.endpos: 12 27# m.lastindex: 3 28# m.lastgroup: sign 29# m.group(1,2): ('hello', 'world') 30# m.groups(): ('hello', 'world', '!') 31# m.groupdict(): {'sign': '!'} 32# m.start(2): 6 33# m.end(2): 11 34# m.span(2): (6, 11) 35# m.expand(r'\2 \1\3'): world hello! 36(2)re.search(pattern, string[, flags]) search 方法与 match 方法极其类似, 区别在于 match()函数只检测 re 是不是在 string 的开始位置匹配,search()会扫描整个 string 查找匹配,match()只有在 0 位置 匹配成功的话才有返回, 如果不是开始位置匹配成功的话, match()就返回 None。 同样,search 方法的返回对象同样 match()返回对象的方法和属性。我们用一个 例子感受一下第 33 页 共 191 页 1#导入 re 模块 2import re 3 4# 将正则表达式编译成 Pattern 对象 5pattern = re.compile(r'world') 6# 使用 search()查找匹配的子串,不存在能匹配的子串时将返回 None 7# 这个例子中使用 match()无法成功匹配 8match = re.search(pattern,'hello world!') 9if match: 10 11 # 使用 Match 获得分组信息 print match.group()12### 输出 ### 13# world(3)re.split(pattern, string[, maxsplit]) 按照能够匹配的子串将 string 分割后返回列表。 maxsplit 用于指定最大分割次数, 不指定将全部分割。我们通过下面的例子感受一下。1import re 2 3pattern = re.compile(r'\d+') 4print re.split(pattern,'one1two2three3four4')第 34 页 共 191 页 5 6### 输出 ### 7# ['one', 'two', 'three', 'four', ''](4)re.findall(pattern, string[, flags]) 搜索 string,以列表形式返回全部能匹配的子串。我们通过这个例子来感受一下1import re 2 3pattern = re.compile(r'\d+') 4print re.findall(pattern,'one1two2three3four4') 5 6### 输出 ### 7# ['1', '2', '3', '4'](5)re.finditer(pattern, string[, flags]) 搜索 string,返回一个顺序访问每一个匹配结果(Match 对象)的迭代器。我们 通过下面的例子来感受一下1import re 2 3pattern = re.compile(r'\d+')第 35 页 共 191 页 4for m in re.finditer(pattern,'one1two2three3four4'): 5 6 7### 输出 ### 8# 1 2 3 4 print m.group(),(6)re.sub(pattern, repl, string[, count]) 使用 repl 替换 string 中每一个匹配的子串后返回替换后的字符串。 当 repl 是一个字符串时,可以使用\id 或\g、\g 引用分组,但不能使用编号 0。 当 repl 是一个方法时,这个方法应当只接受一个参数(Match 对象),并返回一 个字符串用于替换(返回的字符串中不能再引用分组)。 count 用于指定最多替换次数,不指定时全部替换。1import re 2 3pattern = re.compile(r'(\w+) (\w+)') 4s = 'i say, hello world!' 5 6print re.sub(pattern,r'\2 \1', s) 7 8def func(m): 9 10 11print re.sub(pattern,func, s) return m.group(1).title() + ' ' + m.group(2).title()第 36 页 共 191 页 12 13### output ### 14# say i, world hello! 15# I Say, Hello World!(7)re.subn(pattern, repl, string[, count]) 返回 (sub(repl, string[, count]), 替换次数)。1import re 2pattern = re.compile(r'(\w+) (\w+)') 3s = 'i say, hello world!' 4print re.subn(pattern,r'\2 \1', s) 5def func(m): 6 return m.group(1).title() + ' ' + m.group(2).title()7print re.subn(pattern,func, s) 8### output ### 9# ('say i, world hello!', 2) 10# ('I Say, Hello World!', 2) 11 12 13 14第 37 页 共 191 页 155.Python Re 模块的另一种使用方式在上面我们介绍了 7 个工具方法,例如 match,search 等等,不过调用方式都是 re.match, re.search 的方式, 其实还有另外一种调用方式, 可以通过 pattern.match, pattern.search 调用, 这样调用便不用将 pattern 作为第一个参数传入了, 大家想怎 样调用皆可。 函数 API 列表1match(string[, pos[, endpos]]) | re.match(pattern, string[, flags]) 2search(string[, pos[, endpos]]) | re.search(pattern, string[, flags]) 3split(string[, maxsplit]) | re.split(pattern, string[, maxsplit]) 4findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags]) 5finditer(string[, pos[, endpos]]) | re.finditer(pattern, string[, flags]) 6sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]) 7subn(repl, string[, count]) |re.sub(pattern, repl, string[, count])具体的调用方法不必详说了,原理都类似,只是参数的变化不同。小伙伴们尝试 一下吧~ 小伙伴们加油, 即使这一节看得云里雾里的也没关系,接下来我们会通过一些实 战例子来帮助大家熟练掌握正则表达式的。 参考文章:此文章部分内容出自 CNBlogs 转载请注明:静觅 ?Python 爬虫入门七之正则表达式喜欢 (48) or 分享 (1)第 38 页 共 191 页 Python 爬虫入门八之 Beautiful Soup 的用法Python 崔庆才 4 个月前 (03-10) 8015℃ 18 评论 上一节我们介绍了正则表达式, 它的内容其实还是蛮多的,如果一个正则匹配稍 有差池, 那可能程序就处在永久的循环之中,而且有的小伙伴们也对写正则表达 式的写法用得不熟练,没关系,我们还有一个更强大的工具,叫 Beautiful Soup, 有了它我们可以很方便地提取出 HTML 或 XML 标签中的内容, 实在是方便, 这 一节就让我们一起来感受一下 Beautiful Soup 的强大吧。1. Beautiful Soup 的简介简单来说,Beautiful Soup 是 python 的一个库,最主要的功能是从网页抓取数据。 官方解释如下:Beautiful Soup 提供一些简单的、python 式的函数用来处理导航、搜索、 修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的 数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。 Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这 时,Beautiful Soup 就不能自动识别编码方式了。然后,你仅仅需要说明 一下原始编码方式就可以了。 Beautiful Soup 已成为和 lxml、html6lib 一样出色的 python 解释器,为 用户灵活地提供不同的解析策略或强劲的速度。废话不多说,我们来试一下吧~2. Beautiful Soup 安装Beautiful Soup 3 目前已经停止开发,推荐在现在的项目中使用 Beautiful Soup 4, 不过它已经被移植到 BS4 了,也就是说导入时我们需要 import bs4 。所以这里 我们用的版本是 Beautiful Soup 4.3.2 (简称 BS4),另外据说 BS4 对 Python3 的 支持不够好,不过我用的是 Python2.7.7,如果有小伙伴用的是 Python3 版本, 可以考虑下载 BS3 版本。 如果你用的是新版的 Debain 或 Ubuntu,那么可以通过系统的软件包管理来安装, 不过它不是最新版本,目前是 4.2.1 版本1sudo apt-get install Python-bs4第 39 页 共 191 页 如果想安装最新的版本,请直接下载安装包来手动安装,也是十分方便的方法。 在这里我安装的是 Beautiful Soup 4.3.2 Beautiful Soup 3.2.1Beautiful Soup 4.3.2 下载完成之后解压 运行下面的命令即可完成安装1sudo python setup.py install如下图所示,证明安装成功了然后需要安装 lxml第 40 页 共 191 页 1sudo apt-get install Python-lxmlBeautiful Soup 支持 Python 标准库中的 HTML 解析器,还支持一些第三方的解析 器,如果我们不安装它,则 Python 会使用 Python 默认的解析器,lxml 解析器 更加强大,速度更快,推荐安装。3. 开启 Beautiful Soup 之旅在这里先分享官方文档链接,不过内容是有些多,也不够条理,在此本文章做一 下整理方便大家参考。 官方文档4. 创建 Beautiful Soup 对象首先必须要导入 bs4 库1from bs4 import BeautifulSoup我们创建一个字符串,后面的例子我们便会用它来演示1html = &&& 2&html&&head&&title&The Dormouse's story&/title&&/head& 3&body& 4&p class=&title& name=&dromouse&&&b&The Dormouse's story&/b&&/p&第 41 页 共 191 页 5&p class=&story&&Once upon a time there were
and their names were 6&a href=&http://example.com/elsie& class=&sister& id=&link1&&&!-- Elsie --&&/a&, 7&a href=&http://example.com/lacie& class=&sister& id=&link2&&Lacie&/a& and 8&a href=&http://example.com/tillie& class=&sister& id=&link3&&Tillie&/a&; 9and they lived at the bottom of a well.&/p& 10&p class=&story&&...&/p& 11&&&创建 beautifulsoup 对象1soup = BeautifulSoup(html)另外,我们还可以用本地 HTML 文件来创建对象,例如1soup = BeautifulSoup(open('index.html'))上面这句代码便是将本地 index.html 文件打开,用它来创建 soup 对象 下面我们来打印一下 soup 对象的内容,格式化输出1print soup.prettify()第 42 页 共 191 页 1&html& 2&head& 3 &title& 4 The Dormouse's story5 &/title&以上便是输出结果,格式化打印出了它的内容,这个函数经常用到,小伙伴们要 记好咯。5. 四大对象种类Beautiful Soup 将复杂 HTML 文档转换成一个复杂的树形结构,每个节点都是 Python 对象,所有对象可以归纳为 4 种:? Tag ? NavigableString ? BeautifulSoup ? Comment下面我们进行一一介绍 (1)Tag Tag 是什么?通俗点讲就是 HTML 中的一个个标签,例如1&title&The Dormouse's story&/title&1&a class=&sister& href=&http://example.com/elsie& id=&link1&&Elsie&/a&第 43 页 共 191 页 上面的 title a 等等 HTML 标签加上里面包括的内容就是 Tag,下面我们来感 受一下怎样用 Beautiful Soup 来方便地获取 Tags 下面每一段代码中注释部分即为运行结果1print soup.title 2#&title&The Dormouse's story&/title&1print soup.head 2#&head&&title&The Dormouse's story&/title&&/head&1print soup.a 2#&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&1print soup.p 2#&p class=&title& name=&dromouse&&&b&The Dormouse's story&/b&&/p&我们可以利用 soup 加标签名轻松地获取这些标签的内容,是不是感觉比正则表 达式方便多了?不过有一点是, 它查找的是在所有内容中的第一个符合要求的标 签,如果要查询所有的标签,我们在后面进行介绍。 我们可以验证一下这些对象的类型第 44 页 共 191 页 1print type(soup.a) 2#&class 'bs4.element.Tag'&对于 Tag,它有两个重要的属性,是 name 和 attrs,下面我们分别来感受一下 name1print soup.name 2print soup.head.name 3#[document] 4#headsoup 对象本身比较特殊,它的 name 即为 [document],对于其他内部标签,输 出的值便为标签本身的名称。 attrs1print soup.p.attrs 2#{'class': ['title'], 'name': 'dromouse'}在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。 如果我们想要单独获取某个属性,可以这样,例如我们获取它的 class 叫什么第 45 页 共 191 页 1print soup.p['class'] 2#['title']还可以这样,利用 get 方法,传入属性的名称,二者是等价的1print soup.p.get('class') 2#['title']我们可以对这些属性和内容等等进行修改,例如1soup.p['class']=&newClass& 2print soup.p 3#&p class=&newClass& name=&dromouse&&&b&The Dormouse's story&/b&&/p&还可以对这个属性进行删除,例如1del soup.p['class'] 2print soup.p 3#&p name=&dromouse&&&b&The Dormouse's story&/b&&/p&不过,对于修改删除的操作,不是我们的主要用途,在此不做详细介绍了,如果 有需要,请查看前面提供的官方文档 (2)NavigableString 既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字 怎么办呢?很简单,用 .string 即可,例如第 46 页 共 191 页 1print soup.p.string 2#The Dormouse's story这样我们就轻松获取到了标签里面的内容,想想如果用正则表达式要多麻烦。它 的类型是一个 NavigableString,翻译过来叫 可以遍历的字符串,不过我们最好 还是称它英文名字吧。 来检查一下它的类型1print type(soup.p.string) 2#&class 'bs4.element.NavigableString'&(3)BeautifulSoup BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当 作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称,以及属 性来感受一下1print type(soup.name) 2#&type 'unicode'& 3print soup.name 4# [document] 5print soup.attrs 6#{} 空字典第 47 页 共 191 页 (4)Comment Comment 对象是一个特殊类型的 NavigableString 对象,其实输出的内容仍然不 包括注释符号, 但是如果不好好处理它,可能会对我们的文本处理造成意想不到 的麻烦。 我们找一个带注释的标签1print soup.a 2print soup.a.string 3print type(soup.a.string)运行结果如下1&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a& 2Elsie 3&class 'bs4.element.Comment'&a 标签里的内容实际上是注释, 但是如果我们利用 .string 来输出它的内容, 我们 发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。 另外我们打印输出下它的类型,发现它是一个 Comment 类型,所以,我们在使 用前最好做一下判断,判断代码如下1if type(soup.a.string)==bs4.element.Comment: 2 print soup.a.string第 48 页 共 191 页 上面的代码中,我们首先判断了它的类型,是否为 Comment 类型,然后再进行 其他操作,如打印输出。6. 遍历文档树(1)直接子节点要点:.contents .children 属性.contentstag 的 .content 属性可以将 tag 的子节点以列表的方式输出1print soup.head.contents 2#[&title&The Dormouse's story&/title&]输出方式为列表,我们可以用列表索引来获取它的某一个元素1print soup.head.contents[0] 2#&title&The Dormouse's story&/title&.children 它返回的不是一个 list,不过我们可以通过遍历获取所有子节点。 我们打印输出 .children 看一下,可以发现它是一个 list 生成器对象1print soup.head.children 2#&listiterator object at 0x7f&第 49 页 共 191 页 我们怎样获得里面的内容呢?很简单,遍历一下就好了,代码及结果如下1for child in soup.body.children: 2 print child1&p class=&title& name=&dromouse&&&b&The Dormouse's story&/b&&/p& 2 3&p class=&story&&Once upon a time there were
and their names were 4&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&, 5&a class=&sister& href=&http://example.com/lacie& id=&link2&&Lacie&/a& and(2)所有子孙节点知识点:.descendants 属性.descendants .contents 和 .children 属性仅包含 tag 的直接子节点, .descendants 属性可以对所有 tag 的子孙节点进行递归循环,和 children 类似,我们也需要遍历获取其中的内 容。1for child in soup.descendants: 2 print child运行结果如下,可以发现,所有的节点都被打印出来了,先生最外层的 HTML 标签,其次从 head 标签一个个剥离,以此类推。第 50 页 共 191 页 1&html&&head&&title&The Dormouse's story&/title&&/head& 2&body& 3&p class=&title& name=&dromouse&&&b&The Dormouse's story&/b&&/p& 4&p class=&story&&Once upon a time there were
and their names were 5&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&,(3)节点内容知识点:.string 属性如果 tag 只有一个 NavigableString 类型子节点,那么这个 tag 可以使用 .string 得到 子节点。 如果一个 tag 仅有一个子节点,那么这个 tag 也可以使用 .string 方法,输出 结果与当前唯一子节点的 .string 结果相同。 通俗点说就是:如果一个标签里面没有标签了,那么 .string 就会返回标签里面 的内容。如果标签里面只有唯一的一个标签了,那么 .string 也会返回最里面的 内容。例如1print soup.head.string 2#The Dormouse's story 3print soup.title.string 4#The Dormouse's story如果 tag 包含了多个子节点,tag 就无法确定, string 方法应该调用哪个子节点的内 容, .string 的输出结果是 None第 51 页 共 191 页 1print soup.html.string 2# None(4)多个内容知识点: .strings .stripped_strings 属性.strings 获取多个内容,不过需要遍历获取,比如下面的例子1for string in soup.strings: 2 3 4 5 6 7 8 9 10 11 12 13 14 print(repr(string)) # u&The Dormouse's story& # u'\n\n' # u&The Dormouse's story& # u'\n\n' # u'Once upon a time there were
and their names were\n' # u'Elsie' # u',\n' # u'Lacie' # u' and\n' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'\n\n'第 52 页 共 191 页 15 16# u'...' # u'\n'.stripped_strings 输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余 空白内容1for string in soup.stripped_strings: 2 3 4 5 6 7 8 9 10 11 12 print(repr(string)) # u&The Dormouse's story& # u&The Dormouse's story& # u'Once upon a time there were
and their names were' # u'Elsie' # u',' # u'Lacie' # u'and' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'...'(5)父节点知识点: .parent 属性第 53 页 共 191 页 1p = soup.p 2print p.parent.name 3#body1content = soup.head.title.string 2print content.parent.name 3#title(6)全部父节点知识点:.parents 属性通过元素的 .parents 属性可以递归得到元素的所有父辈节点,例如1content = soup.head.title.string 2for parent in content.parents: 3 print parent.name1title 2head 3html 4[document]第 54 页 共 191 页 (7)兄弟节点知识点:.next_sibling .previous_sibling 属性兄弟节点可以理解为和本节点处在统一级的节点,.next_sibling 属性获取了该节 点的下一个兄弟节点,.previous_sibling 则与之相反,如果节点不存在,则返回 None 注意: 实际文档中的 tag 的 .next_sibling 和 .previous_sibling 属性通常是字符串 或空白, 因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白 或者换行1print soup.p.next_sibling 2# 实际该处为空白3print soup.p.prev_sibling 4#None 没有前一个兄弟节点,返回 None5print soup.p.next_sibling.next_sibling 6#&p class=&story&&Once upon a time there were
and their names were 7#&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&, 8#&a class=&sister& href=&http://example.com/lacie& id=&link2&&Lacie&/a& and 9#&a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a&; 10#and they lived at the bottom of a well.&/p& 11#下一个节点的下一个兄弟节点是我们可以看到的节点(8)全部兄弟节点知识点:.next_siblings .previous_siblings 属性通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输 出第 55 页 共 191 页 1for sibling in soup.a.next_siblings: 2 3 4 5 6 7 8 print(repr(sibling)) # u',\n' # &a class=&sister& href=&http://example.com/lacie& id=&link2&&Lacie&/a& # u' and\n' # &a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a& # u'; and they lived at the bottom of a well.' # None(9)前后节点知识点:.next_element .previous_element 属性与 .next_sibling .previous_sibling 不同,它并不是针对于兄弟节点,而是在所有 节点,不分层次 比如 head 节点为1&head&&title&The Dormouse's story&/title&&/head&那么它的下一个节点便是 title,它是不分层次关系的1print soup.head.next_element 2#&title&The Dormouse's story&/title&第 56 页 共 191 页 (10)所有前后节点知识点:.next_elements .previous_elements 属性通过 .next_elements 和 .previous_elements 的迭代器就可以向前或向后访问文档 的解析内容,就好像文档正在被解析一样1for element in last_a_tag.next_elements: 2 print(repr(element))3# u'Tillie' 4# u';\nand they lived at the bottom of a well.' 5# u'\n\n' 6# &p class=&story&&...&/p& 7# u'...' 8# u'\n' 9# None7.搜索文档树(1)find_all( name , attrs , recursive , text , **kwargs ) find_all() 方法搜索当前 tag 的所有 tag 子节点,并判断是否符合过滤器的条件 1)name 参数 name 参数可以查找所有名字为 name 的 tag,字符串对象会被自动忽略掉 A.传字符串 最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup 会 查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的&b&标签第 57 页 共 191 页 1soup.find_all('b') 2# [&b&The Dormouse's story&/b&]1print soup.find_all('a') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&, &a class=&sister& h ref=&http://example.com/lacie& id=&link2&&Lacie&/a&, &a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a&]B.传正则表达式 如果传入正则表达式作为参数,Beautiful Soup 会通过正则表达式的 match() 来匹 配内容.下面例子中找出所有以 b 开头的标签,这表示&body&和&b&标签都应该被 找到1import re 2for tag in soup.find_all(re.compile(&^b&)): 3 print(tag.name)4# body 5# bC.传列表 如果传入列表参数,Beautiful Soup 会将与列表中任一元素匹配的内容返回.下面 代码找到文档中所有&a&标签和&b&标签第 58 页 共 191 页 1soup.find_all([&a&, &b&]) 2# [&b&The Dormouse's story&/b&, 3# &a class=&sister& href=&http://example.com/elsie& id=&link1&&Elsie&/a&, 4# &a class=&sister& href=&http://example.com/lacie& id=&link2&&Lacie&/a&, 5# &a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a&]D.传 True True 可以匹配任何值,下面代码查找到所有的 tag,但是不会返回字符串节点1for tag in soup.find_all(True): 2 print(tag.name)3# html 4# head 5# title 6# body 7# p 8# b 9# p 10# a 11# aE.传方法第 59 页 共 191 页 如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 [4] ,如 果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False 下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返 回 True:1def has_class_but_no_id(tag): 2 return tag.has_attr('class') and not tag.has_attr('id')将这个方法作为参数传入 find_all() 方法,将得到所有&p&标签:1soup.find_all(has_class_but_no_id) 2# [&p class=&title&&&b&The Dormouse's story&/b&&/p&, 3# &p class=&story&&Once upon a time there were...&/p&, 4# &p class=&story&&...&/p&]2)keyword 参数注意:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数 当作指定名字 tag 的属性来搜索,如果包含一个名字为 id 的参 数,Beautiful Soup 会搜索每个 tag 的”id”属性1soup.find_all(id='link2') 2# [&a class=&sister& href=&http://example.com/lacie& id=&link2&&Lacie&/a&]如果传入 href 参数,Beautiful Soup 会搜索每个 tag 的”href”属性第 60 页 共 191 页 1soup.find_all(href=re.compile(&elsie&)) 2# [&a class=&sister& href=&http://example.com/elsie& id=&link1&&Elsie&/a&]使用多个指定名字的参数可以同时过滤 tag 的多个属性1soup.find_all(href=re.compile(&elsie&), id='link1') 2# [&a class=&sister& href=&http://example.com/elsie& id=&link1&&three&/a&]在这里我们想用 class 过滤,不过 class 是 python 的关键词,这怎么办?加个下 划线就可以1soup.find_all(&a&, class_=&sister&) 2# [&a class=&sister& href=&http://example.com/elsie& id=&link1&&Elsie&/a&, 3# &a class=&sister& href=&http://example.com/lacie& id=&link2&&Lacie&/a&, 4# &a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a&]有些 tag 属性在搜索不能使用,比如 HTML5 中的 data-* 属性1data_soup = BeautifulSoup('&div data-foo=&value&&foo!&/div&') 2data_soup.find_all(data-foo=&value&) 3# SyntaxError: keyword can't be an expression第 61 页 共 191 页 但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性 的 tag1data_soup.find_all(attrs={&data-foo&: &value&}) 2# [&div data-foo=&value&&foo!&/div&]3)text 参数 通过 text 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, text 参 数接受 字符串 , 正则表达式 , 列表, True1soup.find_all(text=&Elsie&) 2# [u'Elsie'] 3 4soup.find_all(text=[&Tillie&, &Elsie&, &Lacie&]) 5# [u'Elsie', u'Lacie', u'Tillie'] 6 7soup.find_all(text=re.compile(&Dormouse&)) 8[u&The Dormouse's story&, u&The Dormouse's story&]4)limit 参数 find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不 需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与 SQL 中的 limit 关 键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果. 文档树中有 3 个 tag 符合搜索条件,但结果只返回了 2 个,因为我们限制了返回数 量第 62 页 共 191 页 1soup.find_all(&a&, limit=2) 2# [&a class=&sister& href=&http://example.com/elsie& id=&link1&&Elsie&/a&, 3# &a class=&sister& href=&http://example.com/lacie& id=&link2&&Lacie&/a&]5)recursive 参数 调用 tag 的 find_all() 方法时,Beautiful Soup 会检索当前 tag 的所有子孙节点,如果 只想搜索 tag 的直接子节点,可以使用参数 recursive=False . 一段简单的文档:1&html& 2&head& 3 &title& 4 The Dormouse's story5 &/title& 6&/head& 7...是否使用 recursive 参数的搜索结果:1soup.html.find_all(&title&) 2# [&title&The Dormouse's story&/title&]第 63 页 共 191 页 3 4soup.html.find_all(&title&, recursive=False) 5# [](2)find( name , attrs , recursive , text , **kwargs ) 它与 find_all() 方法唯一的区别是 find_all() 方法的返回结果是值包含一个元素 的列表,而 find() 方法直接返回结果 (3)find_parents() find_parent()find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点 等. find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通 tag 的搜索方法相同,搜索文档搜索文档包含的内容 (4)find_next_siblings() find_next_sibling()这 2 个方法通过 .next_siblings 属性对当 tag 的所有后面解析的兄弟 tag 节点 进行迭代, find_next_siblings() 方法返回所有符合条件的后面的兄弟节 点,find_next_sibling() 只返回符合条件的后面的第一个 tag 节点 (5)find_previous_siblings() find_previous_sibling()这 2 个方法通过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点 进行迭代, find_previous_siblings() 方法返回所有符合条件的前面的兄弟节 点, find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点 (6)find_all_next() find_next()这 2 个方法通过 .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭 代, find_all_next() 方法返回所有符合条件的节点, find_next() 方法返回第一个符 合条件的节点 (7)find_all_previous() 和 find_previous() 这 2 个方法通过 .previous_elements 属性对当前节点前面的 tag 和字符串进行迭 代, find_all_previous() 方法返回所有符合条件的节点, find_previous()方法返回第 一个符合条件的节点注:以上(2)(3)(4)(5)(6)(7)方法参数用法与 find_all() 完 全相同,原理均类似,在此不再赘述。8.CSS 选择器第 64 页 共 191 页 我们在写 CSS 时,标签名不加任何修饰,类名前加点,id 名前加 #,在这里我 们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型 是 list (1)通过标签名查找1print soup.select('title') 2#[&title&The Dormouse's story&/title&]1print soup.select('a') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&, &a class=&sister& h ref=&http://example.com/lacie& id=&link2&&Lacie&/a&, &a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a&]1print soup.select('b') 2#[&b&The Dormouse's story&/b&](2)通过类名查找1print soup.select('.sister') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&, &a class=&sister& h第 65 页 共 191 页 ref=&http://example.com/lacie& id=&link2&&Lacie&/a&, &a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a&](3)通过 id 名查找1print soup.select('#link1') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&](4)组合查找 组合查找即和写 class 文件时,标签名与类名、id 名进行的组合原理是一样的, 例如查找 p 标签中,id 等于 link1 的内容,二者需要用空格分开1print soup.select('p #link1') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&]直接子标签查找1print soup.select(&head & title&) 2#[&title&The Dormouse's story&/title&](5)属性查找 查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同 一节点,所以中间不能加空格,否则会无法匹配到。第 66 页 共 191 页 1print soup.select('a[class=&sister&]') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&, &a class=&sister& h ref=&http://example.com/lacie& id=&link2&&Lacie&/a&, &a class=&sister& href=&http://example.com/tillie& id=&link3&&Tillie&/a&]1print soup.select('a[href=&http://example.com/elsie&]') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&]同样,属性仍然可以与上述查找方式组合,不在同一节点的空格隔开,同一节点 的不加空格1print soup.select('p a[href=&http://example.com/elsie&]') 2#[&a class=&sister& href=&http://example.com/elsie& id=&link1&&&!-- Elsie --&&/a&]好,这就是另一种与 find_all 方法有异曲同工之妙的查找方法,是不是感觉很 方便?总结本篇内容比较多, 把 Beautiful Soup 的方法进行了大部分整理和总结, 不过这还 不算完全,仍然有 Beautiful Soup 的修改删除功能,不过这些功能用得比较少, 只整理了查找提取的方法,希望对大家有帮助!小伙伴们加油! 熟练掌握了 Beautiful Soup,一定会给你带来太多方便,加油吧! 转载请注明:静觅 ?Python 爬虫入门八之 Beautiful Soup 的用法第 67 页 共 191 页 喜欢 (34) or 分享 (2)Python 爬虫实战一之爬取糗事百科段子Python 崔庆才 5 个月前 (02-16) 14121℃ 50 评论 大家好, 前面入门已经说了那么多基础知识了,下面我们做几个实战项目来挑战 一下吧。那么这次为大家带来,Python 爬取糗事百科的小段子的例子。 首先, 糗事百科大家都听说过吧?糗友们发的搞笑的段子一抓一大把,这次我们 尝试一下用爬虫把他们抓取下来。本篇目标1.抓取糗事百科热门段子 2.过滤带有图片的段子 3.实现每按一次回车显示一个段子的发布时间, 发布人, 段子内容, 点赞数。糗事百科是不需要登录的,所以也没必要用到 Cookie,另外糗事百科有的段子 是附图的, 我们把图抓下来图片不便于显示,那么我们就尝试过滤掉有图的段子 吧。 好, 现在我们尝试抓取一下糗事百科的热门段子吧,每按下一次回车我们显示一 个段子。1.确定 URL 并抓取页面代码首先我们确定好页面的 URL 是 http://www.qiushibaike.com/hot/page/1,其中最后 一个数字 1 代表页数,我们可以传入不同的值来获得某一页的段子内容。 我们初步构建如下的代码来打印页面代码内容试试看, 先构造最基本的页面抓取 方式,看看会不会成功1# -*- coding:utf-8 -*2import urllib 3import urllib2第 68 页 共 191 页 4 5 6page = 1 7url = 'http://www.qiushibaike.com/hot/page/' + str(page) 8try: 9 10 11 request = urllib2.Request(url) response = urllib2.urlopen(request) print response.read()12except urllib2.URLError, e: 13 14 15 16 if hasattr(e,&code&): print e.code if hasattr(e,&reason&): print e.reason运行程序,哦不,它竟然报错了,真是时运不济,命途多舛啊1line 373, in _read_status 2raise BadStatusLine(line) 3httplib.BadStatusLine: ''好吧,应该是 headers 验证的问题,我们加上一个 headers 验证试试看吧,将代码 修改如下第 69 页 共 191 页 1# -*- coding:utf-8 -*2import urllib 3import urllib2 4 5page = 1 6url = 'http://www.qiushibaike.com/hot/page/' + str(page) 7user_agent = 'Mozilla/4.0 ( MSIE 5.5; Windows NT)' 8headers = { 'User-Agent' : user_agent } 9try: 10 11 12 request = urllib2.Request(url,headers = headers) response = urllib2.urlopen(request) print response.read()13except urllib2.URLError, e: 14 15 16 17 if hasattr(e,&code&): print e.code if hasattr(e,&reason&): print e.reason嘿嘿,这次运行终于正常了,打印出了第一页的 HTML 代码,大家可以运行下 代码试试看。在这里运行结果太长就不贴了。2.提取某一页的所有段子好,获取了 HTML 代码之后,我们开始分析怎样获取某一页的所有段子。 首先我们审查元素看一下,按浏览器的 F12,截图如下第 70 页 共 191 页 我们可以看到,每一个段子都是&div class=”article block untagged mb15″ id=”…”&…&/div&包裹的内容。 现在我们想获取发布人,发布日期,段子内容,以及点赞的个数。不过另外注意 的是,段子有些是带图片的,如果我们想在控制台显示图片是不现实的,所以我 们直接把带有图片的段子给它剔除掉,只保存仅含文本的段子。 所以我们加入如下正则表达式来匹配一下,用到的方法是 re.findall 是找寻所有 匹配的内容。方法的用法详情可以看前面说的正则表达式的介绍。 好,我们的正则表达式匹配语句书写如下,在原来的基础上追加如下代码第 71 页 共 191 页 1content = response.read().decode('utf-8') 2pattern = re.compile('&div.*?class=&author.*?&.*?&a.*?&/a&.*?&a.*?&(.*?)&/a&.*?&div.*?class'+ 3 4e.S) 5items = re.findall(pattern,content) 6for item in items: print item[0],item[1],item[2],item[3],item[4] '=&content&.*?title=&(.*?)&&(.*?)&/div&(.*?)&div class=&stats.*?class=&number&&(.*?)&/i&',r现在正则表达式在这里稍作说明 1).*? 是一个固定的搭配,.和*代表可以匹配任意无限多个字符,加上?表示使 用非贪婪模式进行匹配, 也就是我们会尽可能短地做匹配,以后我们还会大量用 到 .*? 的搭配。 2)(.*?)代表一个分组,在这个正则表达式中我们匹配了五个分组,在后面的遍 历 item 中,item[0]就代表第一个(.*?)所指代的内容,item[1]就代表第二个(.*?) 所指代的内容,以此类推。 3)re.S 标志代表在匹配时为点任意匹配模式,点 . 也可以代表换行符。 现在我们可以看一下部分运行结果儒雅男神
14:34:42 小时候一个一个拆着放的举个爪? &div class=”thumb”& &a href=”/article/?list=hot&s=4747301″ target=”_blank” onclick=”_hmt.push([‘_trackEvent’, ‘post’, ‘click’, ‘signlePost’])”& &img src=” http://pic.qiushibaike.com/system/pictures/418/medium /app.jpg” alt=”糗事#″ /& &/a& &/div&第 72 页 共 191 页 7093 奇怪的名字啊
14:49:16 回家的路,你追我赶,回家的心情和窗外的阳光一样灿烂。一路向前,离亲 人越来越近了。哪里有爸妈哪里才是家,希望所有糗友的爸爸妈妈都身体健 康??. 4803这是其中的两个段子,分别打印了发布人,发布时间,发布内容,附加图片以及 点赞数。 其中,附加图片的内容我把图片代码整体抠了出来,这个对应 item[3],所以我 们只需要进一步判断 item[3]里面是否含有 img

我要回帖

更多关于 爬爬虫 的文章

 

随机推荐