这一篇详细介绍一下如何使用beautifulsoup或正则表达式来提取网页中的信息。
目标网站:猫眼电影-->榜单-->Top100榜
预期效果:抓取Top100榜中的数据,并存储到mysql数据库
1. 分析网页源码

可以看出每部电影信息都包含在一对<dd>...</dd>标签中,
所以第一步可以通过beautifulsoup库解析出所有<dd>标签对,
然后再从<dd>标签对中依次解析排名所在的<i>标签,电影名所在的<p>标签,上映时间所在的<p>标签以及分数所在的<p>标签
2. 构造请求url
url = start_url + '?offset=' + str(10 * i) 3. 以第一页为例,提取信息
# coding: utf-8# author: hmkfrom bs4 import BeautifulSoupimport requestsimport bs4url = 'http://maoyan.com/board/4'header = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","Accept-Encoding": "gzip, deflate, sdch","Accept-Language": "zh-CN,zh;q=0.8","Cache-Control": "max-age=0","Connection": "keep-alive","Host": "maoyan.com","Referer": "http://maoyan.com/board","Upgrade-Insecure-Requests": "1","User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36"}r = requests.get(url, headers=header)r.encoding = r.apparent_encodinghtml = r.textsoup = BeautifulSoup(html, 'html.parser')# print(soup.find_all('dd'))list=[] # 定义一个列表,保存所有电影数据,一定不要定义在循环里面,不然每次都会清空,最后只会留下最后一部电影的数据for dd in soup.find_all('dd'):index = dd.i.string # 电影排名# print(index)movie = dd.find('p', class_='name').string # 电影名称# print(movie.string)release_times = dd.find('p', class_='releasetime') # 上映时间release_time = release_times.string# print(release_time.string)s = dd.find('p', class_='score').contents # 分数score = s[0].string+s[1].string # 把分数的整数部分和小数部分拼接list.append([index,movie,release_time,score]) # 把每一部电影的排名、名称、上映时间、分数添加到一个列表,再追加到一个大列表print(list)
上述代码的重点在于for循环中信息是如何提取,然后组合的,思路如下:
(1)先提取出页面中所有的<dd>标签对,通过for循环把每组<dd>标签赋给一个dd变量,每一个dd变量都是一个bs4元素的Tag对象;
(2)得到dd标签的返回对象后,可以直接使用find方法来提取dd标签的子标签
开始的时候陷入了一个误区,因为打印出的dd内容是标签元素,然后就想着能不能再把它传进beautifulsoup,
生成一个新的beautifulsoup对象,实际证明不行,因为dd的类型已经是<class 'bs4.element.Tag'>了,而之前传进去的html=r.text的类型是<class 'str'>,很明显不能这样干!!
所以想不通时就打印一下对象类型看看是啥
(3)提取排名
使用 dd.i.string,dd.i表示提取dd标签下的第一个i标签,刚好排名信息就在dd标签下的第一个i标签,加上.string,表示提取文本
(4)提取电影名称
使用 dd.find('p', class_='name').string提取dd标签下class属性为name的p标签,因为电影名称就在这个p标签
(5)提取上映时间
使用 dd.find('p', class_='releasetime')
(6)提取分数
因为分数分为2部分,整数部分和小数部分,且分别属于一个p标签下的i标签,
这样用tag.contents方法(tag的 .contents 属性可以将tag的子节点以列表的方式输出),
然后再将2部分拼接形成完整分数,如下:
dd.find('p',class_='score').contents[0].string+dd.find('p', class_='score').contents[1].string
看一下上述代码打印的内容

dd的类型

其实通过beautiful获取的html标签数据,都是bs4.element.Tag,也就是bs4的Tag对象
有了dd标签的内容后,再分别提取排名、名称等信息就方便了
注意:
在运行这段代码时,提取分数那里(第32行),遇到了一个错误


if isinstance(dd.find('p', class_='score'), bs4.element.Tag):s = dd.find('p', class_='score').contents # 分数#print(s)score = s[0].string + s[1].string # 把分数的整数部分和小数部分拼接else:score = "暂无分数"
4. 将代码简单封装,并将数据插入到数据库
# coding: utf-8# author: hmkimport requestsfrom bs4 import BeautifulSoupimport bs4import pymysql.cursorsdef get_html(url, header):try:r = requests.get(url=url, headers=header, timeout=20)r.encoding = r.apparent_encodingif r.status_code == 200:return r.textelse:return Noneexcept:return Nonedef get_data(html, list_data):soup = BeautifulSoup(html, 'html.parser')dd = soup.find_all('dd')for t in dd:if isinstance(t, bs4.element.Tag): # 判断t是否为bs4的tag对象(可能存在空格)ranking = t.i.string # 排名movie = t.find('p', class_='name').stringrelease_time= t.find('p', class_='releasetime').stringif isinstance(t.find('p', class_='score'), bs4.element.Tag):"""判断是否有class属性为score的p标签"""score = t.find('p', class_='score').contents[0].string + t.find('p', class_='score').contents[1].stringelse:score = "暂无得分"print(score)list_data.append([ranking, movie, release_time, score])def write_sql(data):conn = pymysql.connect(host='localhost',user='root',password='123456',db='test',charset='utf8')cur = conn.cursor()for i in data:"""这里的data参数是指处理后的列表数据(是一个大列表,包含所有电影信息,每个电影信息都存在各自的一个列表中;对大列表进行迭代,提取每组电影信息,这样提取到的每组电影信息都是一个小列表,然后就可以把每组电影信息写入数据库了)"""movie = i # 每组电影信息,这里可以看做是准备插入数据库的每组电影数据sql = "insert into maoyan_movie(ranking, movie, release_time, score) values(%s, %s, %s, %s)" # sql插入语句 插入数据时,注意数据库字段的类型及长度是否满足插入值得要求try:cur.execute(sql, movie) # 执行sql语句,movie即是指要插入数据库的数据conn.commit() # 插入完成后,不要忘记提交操作print('导入成功')except:print('导入失败')cur.close() # 关闭游标conn.close() # 关闭连接def main():start_url = 'http://maoyan.com/board/4'depth = 2 # 爬取深度(翻页)header = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","Accept-Encoding": "gzip, deflate, sdch","Accept-Language": "zh-CN,zh;q=0.8","Cache-Control": "max-age=0","Connection": "keep-alive","Host": "maoyan.com","Referer": "http://maoyan.com/board","Upgrade-Insecure-Requests": "1","User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36"}for i in range(depth):url = start_url + '?offset=' + str(10 * i)html = get_html(url, header)list_data = []get_data(html, list_data)#print(list_data)write_sql(list_data)# print(list_data)if __name__ == "__main__":main()
运行程序,到数据库查看结果

5. 使用正则表达式提取信息
def get_data(html, list_data):pattern = re.compile(r'<dd>.*?<i.*?>(\d+)</i>.*?' # 匹配电影排名r'<p class="name"><a.*?data-val=".*?">(.*?)' # 匹配电影名称r'</a>.*?<p.*?class="releasetime">(.*?)</p>' # 匹配上映时间r'.*?<i.*?"integer">(.*?)</i>' # 匹配分数的整数位r'.*?<i.*?"fraction">(.*?)</i>.*?</dd>', re.S) # 匹配分数小数位m = pattern.findall(html)print(m)for i in m: # 因为匹配到的所有结果会以列表形式返回,每部电影信息以元组形式保存,所以可以迭代处理每组电影信息ranking = i[0] # 提取一组电影信息中的排名movie = i[1] # 提取一组电影信息中的名称release_time = i[2] # 提取一组电影信息中的上映时间score = i[3] + i[4] # 提取一组电影信息中的分数,这里把分数的整数部分和小数部分拼在一起list_data.append([ranking, movie, release_time, score]) # 每提取一组电影信息就放到一个列表中,同时追加到一个大列表里,这样最后得到的大列表就包含所有电影信息




