书中推荐的实践项目如下,我首先打算做一个类似的项目 。
本章实践项目的目的是获取豆瓣电影TOP250的所有电影的名称,网页地址为:https://movie.douban.com/top250。在此爬虫中,将请求头定制为实际浏览器的请求头。
动态网页是与静态网页相对应的,也就是说,网页URL的后缀不是.htm、.html、.shtml、.xml等静态网页的常见形式,
而是以.asp、.jsp、.php、.perl、.cgi等形式为后缀,并且在动态网页网址中有一个标志性的符号——“?”。
User-agent: *
Disallow: /subject_search
Disallow: /search
Disallow: /new_subject
Disallow: /service/iframe
Disallow: /j/
Sitemap: http://www.douban.com/sitemap_index.xml
Sitemap: http://www.douban.com/sitemap_updated_index.xml
User-agent: Wandoujia Spider
Disallow: /
<sitemap>
<loc>https://www.douban.com/sitemap17.xml.gzloc>
<lastmod>2019-08-31T20:56:17Zlastmod>
sitemap>
- <url>
<loc>https://music.douban.com/subject/1858957/loc>
<priority>0.000631481287316priority>
<changefreq>monthlychangefreq>
url>
显然要从这一大堆文件里找出我们的目标暂时很麻烦。在有能力对这一长串东西爬取之前,还是只能先相信书了(可以爬取豆瓣电影TOP250)。
2-2-2是编写的经历,涉及soup.a.text.strip()(或者豆瓣电影top250源码里的soup.a.scan.text.strip())为什么改写成 i.a.text.replace(’\n’, ‘’)的解释。
源码:
import requests
from bs4 import BeautifulSoup
def sample_get_spider(link, headers, timeout = 20):
"""
:param link: 爬取网站的url
:param headers: 爬取时使用的请求头
:param timeout: 爬取时间限制,默认20(单位:秒)
:return: response响应对象
"""
req = requests.get(link, headers=headers, timeout=timeout)
print('页面响应码{0}'.format(req.status_code))
return req
def touban_book_top250_spider():
book_list = list()
for i in range(0, 10):
link = 'https://book.douban.com/top250?start=' + str(i * 25)
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/'
'537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Host': 'book.douban.com'}
req = sample_get_spider(link, headers)
soup = BeautifulSoup(req.text, "lxml") # 使用BeautifulSoup解析这段代码
div_list = soup.find_all("div", class_="pl2")
for i in div_list:
book = i.a.text.replace('\n', '')
book_list.append(book.replace(' ', ''))
return book_list
def main():
book_list = touban_book_top250_spider()
print(book_list)
if __name__ == '__main__':
main()
结果
页面响应码200
页面响应码200
页面响应码200
页面响应码200
页面响应码200
页面响应码200
页面响应码200
页面响应码200
页面响应码200
页面响应码200
# 这是手动输入的说明,测试结果中原本是一堆书名 ,出于**,用该说明代替#
实际上到目前为止,某书还没怎么介绍获取标题时用到的BeautifulSoup,因为后面也许会有介绍(他没写的话只好自己弄了)这里只是运用笔记(3)中涉及的例子,相关代码如下:
from bs4 import BeautifulSoup
……
soup_1 = BeautifulSoup(req_1.text, "lxml") #使用BeautifulSoup解析这段代码
title_1 = soup_1.find("h1", class_ = "post-title").a.text.strip()
根据笔记(3)中积累的经验,先普普通通的看看网页源码 ,这里放出前两名图书的代码片段(修改了缩进):
<a class="nbg" href="https://book.douban.com/subject/1770782/"
onclick="moreurl(this,{i:'0'})"
……
<img src="https://img3.doubanio.com/view/subject/m/public/s1727290.jpg" width="90" />
……
<div class="pl2">
<a href="https://book.douban.com/subject/1770782/"
onclick="moreurl(this,{i:'0'})" title="追风筝的人"
>
……
……
……
<a class="nbg" href="https://book.douban.com/subject/25862578/"
onclick="moreurl(this,{i:'1'})"
……
<img src="https://img3.doubanio.com/view/subject/m/public/s27264181.jpg" width="90" />
……
<div class="pl2">
<a href="https://book.douban.com/subject/25862578/"
onclick="moreurl(this,{i:'1'})" title="解忧杂货店"
>
而笔记(3)对应的是(同样改了缩进)
<h1 class="post-title"><a href="http://www.santostang.com/2018/07/15/4-3-%e9%80%9a%e8%bf%87
selenium%e6%a8%a1%e6%8b%9f%e6%b5%8f%e8%a7%88%e5%99%a8%e6%8a%93%e5%8f%96/"
>第四章 – 4.3 通过selenium 模拟浏览器抓取
这次的title在<>里面,不知道有没有影响,总之先试试。
结果果然炸了:
title = soup.find("div", class_="pl2").a.text.strip()
AttributeError: 'NoneType' object has no attribute 'a'
于是参观了答案(虽然是电影的):
div_list = soup.find_all('div', class_= 'hd')
for each in div_list:
movie = each.a.span.text.strip()
movie_list.append(movie)
改成book以后还是出错:
book = i.a.span.text.strip()
AttributeError: 'NoneType' object has no attribute 'text'
但是div_list可以打印出来,以其中一项为例:
<div class="pl2">
<a href="https://book.douban.com/subject/1770782/" onclick=""moreurl(this,{i:'0'})"" title="# 原本是书名 #">
# 原本是书名 #
</a>
<img alt="可试读" src="https://img3.doubanio.com/pics/read.gif" title="可试读"/>
<br/>
<span style="font-size:12px;">The Kite Runner</span>
</div>
而电影代码却可以正常使用,于是我决定查看豆瓣电影top250的代码:
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
<span class="title"># 原本是电影名 #
<span class="title"> / The Shawshank Redemption</span>
<span class="other"> / # 原本是其他地区的电影译名 #
span类……a.span原来是指这个……
将i.a.scan.text.strip()改为i.a.text.strip()后
得到了这样的结果:
# 这里本来应该是25个书名,并且有些书名之间存在大量“\n”和空格 #
这个结果并不是那么令人满意,如果只有25个是因为我只爬取了第一页,而大量“\n”和空格就有点奇怪了。
于是我又看了一遍网页源码,发现有大量“\n”和空格的条目确实有些不同:
<a href="https://book.douban.com/subject/1084336/" onclick=""moreurl(this,{i:'2'})"" title="# 原本是书名 #">
# 原本是书名 #
</a>
<a href="https://book.douban.com/subject/2567698/" onclick=""moreurl(this,{i:'6'})"" title="# 原本是书名 #
">
# 原本是书名 #
<span style="font-size:12px;"> : # 原本是书的说明 #
</a>
没错,这些条目在< a href >和< /a >之间多了一条 。
通过这一点,不难猜出i.a.text.strip()的具体含义:
i是Tag objects列表(该列表由soup.find_all返回,soup.find返回的是这个列表的首个元素)的一个元素 ;
text是a(或者scan或者a里的其他类甚至a里类里的类……)的一个属性;
strip() 是将text中的< a h……> 到 < \a >之间所有<>内的内容删去并且删掉开头和结尾的空格换行(注意这个a不是类a,而是一个标记,当然可以换成别的字母)。
为了验证我的猜想,我参看了strip()的文档,结果发现有一大堆。
其中一个是python自带的,说明如下Strip leading and trailing bytes contained in the argument. If the argument is omitted, strip trailing ASCII whitespace.(机翻:剥去参数中包含的前导和尾随字节。如果省略该参数,则删除尾随ASCII空格)。
之后我又尝试去掉.strip(),发现结果变成了‘\n 追风筝的人\n\n \n’这种样子。
我确实猜错了,text才是负责将文本中的< a h……> 到 < \a >之间所有<>内的内容删去的,所以有关i.a.text.strip()的具体含义应该修正如下(划线部分):
text是将a(或者scan或者a里的其他类甚至a里类里的类……)的文本中的< a h……> 到 < \a >之间所有<>内的内容删去得到的一个字符串;
strip() 是将字符串开头和结尾的空格换行删掉的函数。
为了消除多余的空格换行我采用了两次replace:
book = i.a.text.replace('\n', '')
book_list.append(book.replace(' ', ''))
最后提一下爬取全部十页的方法。虽然第一页叫https://book.douban.com/top250?icn=index-book250-all,后面的看起来才遵循一定规律(https://book.douban.com/top250?start=【i*25】,i在(0,10)之间),实际上也可以用这个规律来套第一页,也就是i取0(https://book.douban.com/top250?start=0)。
作者:naobing111
链接:https://zhidao.baidu.com/question/228717377.html ↩︎