For what it’s worth, it’s never too late. ——《返老还童》
有意义的事,什么时候做都不迟。
最近因为新型冠状病毒,在家宅着实在无聊,想着找些电影看。于是乎…
站点分析
我们需要抓取的目标站点为 https://maoyan.com/board/4 ,打开之后便可以查看榜单。如下图,排名第一的电影是霸王别姬。

将网页滑到最下方,发现有分页,点击切换到第2页,观察URL的变化。发现页面的URL变成了 https://maoyan.com/board/4?offset=10 ,如下图:

比之前的URL多了一个offset参数,而目前显示的结果是排名11 ~ 20的电影,初步推断这是一个偏移量的参数。再点击下一页,offset参数变成了20,显示的结果是排名21 ~ 30的电影。多次切换页码offset都有改变,由此得出规律,offset代表偏移量值,如果偏移量为n,则显示的是排名n+1 ~ n+10的电影。也就是说Top100我们只需要分开请求10次即可,而10次的参数分别设置为0、10、20…90即可。
抓取单页源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import requests from requests.exceptions import RequestException
def get_one_page(): try: url = "http://maoyan.com/board/4?offset={0}".format(0) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/80.0.3987.87 Safari/537.36' } response = requests.get(url, headers=headers) if response.status_code == 200: print(response.text) return None except RequestException: return None
def main(): get_one_page()
if __name__ == "__main__": main()
|
运行即可得到网页源码。获取源码之后,就需要解析页面,提提取出我们想要的信息。
解析单页源码
我们使用Chrome浏览器,按下F12在开发者模式下的Network监听组件中查看源代码。查看其中一个条目的源代码。一部电影的信息对的是一个dd节点,我们可以使用正则表达式来提取里面电影的信息。首先排名信息是在class为board-index的i节点内;第二个img节点的data-src属性是图片的链接;再往后,电影名称在后面的p节点内,class为name;主演、发布时间、评分等内容以此类推。如图所示:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import re
def parse_one_page(html): pattern = re.compile( '<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?' 'releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>', re.S ) items = re.findall(pattern, html) for item in items: yield { 'index': item[0], 'image': item[1], 'title': item[2].strip(), 'actor': item[3].strip()[3:] if len(item[3]) > 3 else '', 'time': item[4].strip()[5:] if len(item[4]) > 5 else '', 'score': item[5].strip() + item[6].strip() }
def main(): html = get_one_page() for item in parse_one_page(html): print(item)
if __name__ == "__main__": main()
|
保存到文件中
有两种方式:一种是保存到txt文件中,另一种是保存到csv文件中,可根据需求选择其中一中。
保存到txt文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import json
def write_to_txtFile(content): with open('result.txt', 'a', encoding='utf-8') as f: f.write(json.dumps(content, ensure_ascii=False) + '\n')
def main(): html = get_one_page(url) for item in parse_one_page(html): write_to_txtfile(item)
if __name__ == "__main__": main()
|
保存到csv文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import csv
def write_to_csvFile(content): with open("MovieResult.csv", 'a', encoding='utf-8', newline='') as f: fieldnames = ["index", "image", "title", "actor", "time", "score"] writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(content)
def main(offset): html = get_one_page(url) rows = [] for item in parse_one_page(html): rows.append(item) write_to_csvfile(rows)
if __name__ == "__main__": main()
|
抓取多个页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def main(offset): url = 'http://maoyan.com/board/4?offset=' + str(offset) html = get_one_page(url) rows = [] for item in parse_one_page(html): rows.append(item) write_to_csvfile(rows)
if __name__ == "__main__": for i in range(10): main(offset=i * 10) time.sleep(2)
|
整合完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import re import csv import time import json import requests from requests.exceptions import RequestException
def get_one_page(url): try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/80.0.3987.87 Safari/537.36' } response = requests.get(url, headers=headers) if response.status_code == 200: return response.text return None except RequestException: return None
def parse_one_page(html): pattern = re.compile( '<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?' 'releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>', re.S ) items = re.findall(pattern, html) for item in items: yield { 'index': item[0], 'image': item[1], 'title': item[2].strip(), 'actor': item[3].strip()[3:] if len(item[3]) > 3 else '', 'time': item[4].strip()[5:] if len(item[4]) > 5 else '', 'score': item[5].strip() + item[6].strip() }
def write_to_txtFile(content): with open('MovieTop100.txt', 'a', encoding='utf-8') as f: f.write(json.dumps(content, ensure_ascii=False) + '\n')
def write_to_csvField(filename): with open("MovieTop100.csv", 'a', encoding='utf-8', newline='') as f: writer = csv.DictWriter(f, filename) writer.writeheader()
def write_to_csvRows(content, filename): with open("MovieTop100.csv", 'a', encoding='utf-8', newline='') as f: writer = csv.DictWriter(f, filename) writer.writerows(content)
def main(offset, fieldnames): url = 'http://maoyan.com/board/4?offset={0}'.format(offset) html = get_one_page(url) rows = [] for item in parse_one_page(html): rows.append(item) write_to_csvRows(rows, fieldnames)
if __name__ == "__main__": fieldnames = ["index", "image", "title", "actor", "time", "score"] write_to_csvField(fieldnames) for i in range(10): main(offset=i * 10, fieldnames=fieldnames) time.sleep(1)
|
最终效果展示

PS: 本文参考资料《Python3网络开发实战》–崔庆才。