「2022 年」崔庆才 Python3 爬虫教程 - 基础案例爬取实战( 三 )


由于输出内容比较多,这里只贴了一部分 。
可以看到,这里程序首先爬取了第一页列表页,然后得到了对应详情页的每个 URL,接着再接着爬第二页、第三页,一直到第十页,依次输出了每一页的详情页 URL 。这样,我们就成功获取到了所有电影的详情页 URL 啦 。
4. 爬取详情页
现在我们已经可以成功获取所有详情页 URL 了,那么下一步当然就是解析详情页并提取出我们想要的信息了 。
我们首先观察一下详情页的 HTML 代码吧,如图所示:
经过分析,我们想要提取的内容和对应的节点信息如下:
封面:是一个 img 节点,其 class 属性为 cover 。名称:是一个 h2 节点,其内容便是名称 。类别:是 span 节点,其内容便是类别内容,其外侧是 button 节点,再外侧则是 class 为 categories 的 div 节点 。上映时间:是 span 节点,其内容包含了上映时间,其外侧是包含了 class 为 info 的 div 节点 。另外提取结果中还多了「上映」二字,我们可以用正则表达式把日期提取出来 。评分:是一个 p 节点,其内容便是评分,p 节点的 class 属性为 score 。剧情简介:是一个 p 节点,其内容便是剧情简介,其外侧是 class 为 drama 的 div 节点 。
看似有点复杂是吧,不用担心,有了正则表达式,我们可以轻松搞定 。
接着我们来实现一下代码吧 。
刚才我们已经成功获取了详情页 URL,接着当然是定义一个详情页的爬取方法了,实现如下:
def scrape_detail(url): return scrape_page(url)
这里定义了一个 scrape_detail 方法,接收一个 url 参数,并通过调用 scrape_page 方法获得网页源代码 。由于我们刚才已经实现了 scrape_page 方法,所以在这里我们不用再写一遍页面爬取的逻辑了,直接调用即可,做到了代码复用 。
另外有人会说,这个 scrape_detail 方法里面只调用了 scrape_page 方法,别没有别的功能,那爬取详情页直接用 scrape_page 方法不就好了,还有必要再单独定义 scrape_detail 方法吗?有必要,单独定义一个 scrape_detail 方法在逻辑上会显得更清晰,而且以后如果我们想要对 scrape_detail 方法进行改动,比如添加日志输出,比如增加预处理,都可以在 scrape_detail 里面实现,而不用改动 scrape_page 方法,灵活性会更好 。
好了,详情页的爬取方法已经实现了,接着就是详情页的解析了,实现如下:
def parse_detail(html): cover_pattern = re.compile(\'(.*?)\".*?>\', re.S) name_pattern = re.compile(\'<h2.*?>(.*?)</h2>\') categories_pattern = re.compile(\'<button.*?category.*?<span>(.*?)</span>.*?</button>\', re.S) published_at_pattern = re.compile(\'(\\d{4}-\\d{2}-\\d{2})\\s?上映\') drama_pattern = re.compile(\'<div.*?drama.*?>.*?<p.*?>(.*?)</p>\', re.S) score_pattern = re.compile(\'<p.*?score.*?>(.*?)</p>\', re.S) cover = re.search(cover_pattern, html).group(1).strip() if re.search(cover_pattern, html) else None name = re.search(name_pattern, html).group(1).strip() if re.search(name_pattern, html) else None categories = re.findall(categories_pattern, html) if re.findall(categories_pattern, html) else [] published_at = re.search(published_at_pattern, html).group(1) if re.search(published_at_pattern, html) else None drama = re.search(drama_pattern, html).group(1).strip() if re.search(drama_pattern, html) else None score = float(re.search(score_pattern, html).group(1).strip()) if re.search(score_pattern, html) else None return { \'cover\': cover, \'name\': name, \'categories\': categories, \'published_at\': published_at, \'drama\': drama, \'score\': score }
这里我们定义了 parse_detail 方法用于解析详情页,它接收一个参数为 html,解析其中的内容,并以字典的形式返回结果 。每个字段的解析情况如下所述:
cover:封面,其值是带有 cover 这个 class 的 img 节点的 src 属性的值,所有直接 src 的内容使用 (.*?) 来表示即可,在 img 节点的前面我们再加上一些区分位置的标识符,如 item 。由于结果只有一个,写好正则表达式后用 search 方法提取即可 。name:名称,其值是 h2 节点的文本值,我们直接在 h2 标签的中间使用 (.*?) 表示即可 。由于结果只有一个,写好正则表达式后同样用 search 方法提取即可 。categories:类别,我们注意到每个 category 的值都是 button 节点里面的 span 节点的值,所以我们写好表示 button 节点的正则表达式后,再直接在其内部的 span 标签的中间使用 (.*?) 表示即可 。由于结果有多个,所以这里使用 findall 方法提取,结果是一个列表 。published_at:上映时间,由于每个上映时间信息都包含了「上映」二字,另外日期又都是一个规整的格式,所以对于这个上映时间的提取,我们直接使用标准年月日的正则表达式 (\\d{4}-\\d{2}-\\d{2}) 表示即可 。由于结果只有一个,直接使用 search 方法提取即可 。drama:直接提取 class 为 drama 的节点内部的 p 节点的文本即可,同样用 search 方法可以提取 。score:直接提取 class 为 score 的 p 节点的文本即可,但由于提取结果是字符串,所以我们还需要把它转成浮点数,即 float 类型 。