爬虫:模拟浏览器打开网页,获取网页中我们想要的那部分数据
url :https://movie.douban.com/top250
任务:爬取豆瓣电影top250的基本信息,包括:电影名称、豆瓣评分、评价数、电影概况、电影链接
Screen Shot 2021-11-24 at 3.32.36 PM.png
根据一个网页的链接,找到下一个网页:爬行——获取——爬行
Screen Shot 2021-11-24 at 3.34.49 PM.png
搜索引擎:爬取+索引
基本流程

准备工作
通过浏览器查看分析目标网页,学习编程基础规范
获取数据
通过HTTP库向目标站点发起请求,请求可以包含额外的header信息,如果服务器能正常响应,会得到一个Response,便是多要获取的页面内容
解析内容
得到的内容可能是HTML,json等格式,可以用页面解析库,正则表达式等进行解析
保存数据
保存形式多样,可保存为文本,也可保存到数据库,或者保存特定格式的文件

  1. 网页解析
    1. userAgent: 表明你是一个什么版本的浏览器
    2. cookie:存储 读取,用于爬取登陆后才能获取的内容
    3. headers:我们向网页发送请求时,向服务器发送的消息,服务器通过headers验证我们的身份

学习urllib库

  1. import urllib.request
  2. ## 获取一个get请求
  3. response = urllib.request.urlopen("http://www.baidu.com")
  4. ## 返回一个对象,可以用read()函数获取它的内容,并且用decode来解码,
  5. ## 中文通常用utf-8
  6. print(response.read().decode('utf-8'))
  7. ## 获取一个post请求,模拟用户登陆
  8. ## 测试网站:http://httpbin.org/
  9. import urllib.parse
  10. data = bytes(urllib.parse.urlencode({'hello':'world'}),encoding='utf-8') ## 传入用户名、密码
  11. ## 模拟浏览器发送请求,需要用post的方法来封装数据
  12. response = urllib.request.urlopen('http://httpbin.org/post',data=data)
  13. print(response.read().decode('utf-8'))
  14. ## time out 如果不响应,就pass
  15. ## 超时处理 try catch
  16. try:
  17. response = urllib.request.urlopen('http://httpbin.org/',timeout=0.01)
  18. print(response.read().decode('utf-8'))
  19. except urllib.error.URLError as e:
  20. print('time out!')
  21. # 测试豆瓣 418 被发现是爬虫,需要把url、headers等封装起来,伪装成一个浏览器
  22. headers = {
  23. "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"
  24. }
  25. url = 'https://www.douban.com'
  26. ## 封装一个请求,模拟浏览器发出这个请求。
  27. req = urllib.request.Request(url=url,headers=headers,method='POST')
  28. response = urllib.request.urlopen(req)
  29. print(response.read().decode('utf-8'))

beautiful Soup 学习

  1. beautiful Soup中解析的数据类型 ```python “”” BeautifulSoup4将复杂的html文档转换成一个复杂的树形结构,每个节点都是python对象,所有对象可以 归纳成4种 -Tag -NavigableString ## 常用 -BeautifulSoup ## 常用 -Comment “”” from bs4 import BeautifulSoup file = open(‘../豆瓣/baidu.html’,’rb’) html = file.read() bs = BeautifulSoup(html,’html.parser’)

1. Tag 标签及其内容 默认拿到它所找到的第一个内容

print(bs.title) print(bs.a) print(bs.head)

print(bs.title.string) print(type(bs.title.string))

2. NavigableString 标签里的内容(字符串)

百度一下,你就知道

print(bs.a.attrs) ## 打印标签的属性,用字典存储

{‘class’: [‘mnav’], ‘href’: ‘http://news.baidu.com‘, ‘name’: ‘tj_trnews’}

3. BeautifulSoup 表示整个文档

print(type(ba)) # 一个类

4. Comment 是一个特殊的NavigableString,输出的内容不包括注释符号

print(bs.a.string) print(type(bs.a.string)

  1. 2. 文档的遍历
  2. ```python
  3. #1.contents: 获取Tag 的所有子节点,返回一个list
  4. print(bs.head.contents) ## 返回是head的内容,存储在一个list中,可以用下表取出
  5. print(bs.head.contents[1]) ## 用列表索引来获取它的某一个元素
  6. # 2. childern: 获取Tag 的所有子节点,返回一个生成器
  7. for child in bs.body.children:
  8. print(child)
  9. # 3. decendants: 获取tag的所有子孙节点
  10. # 4. strings :如果tag中包含多个字符串,即在子孙节点中所有内容,可以用此获取,然后进行遍历
  11. # 5. stripped_strings : 与strings用法一致,只不过可以去除那些多余的空白内容
  12. # 5. parent:获取tag的父节点
  13. ## 等各种乱七八糟的亲戚节点
  1. 文档的搜索

    1. # 1. find_all() # 字符串过滤: 会查找与字符串完全匹配的的内容 返回一个list
    2. t_list = bs.find_all('a')
    3. print(t_list)
    4. #2.正则表达式搜索: 使用search()方法搜索
    5. import re
    6. t_list= bs.find_all(re.compile("a"))
    7. print(t_list) # 带a的都找出来了,需要精确匹配
    8. # 3.方法 传入一个函数(方法),根据函数的要求来搜索
    9. def name_is_exists(tag):
    10. return tag.has_attr("name")
    11. t_list = bs.find_all(name_is_exists) ## 函数作为一个参数传入
    12. print(t_list)
    13. for item in t_list:
    14. print(item)
    15. #4. kwargs 指定参数
    16. t_list = bs.find_all(id='head')
    17. t_list = bs.find_all(class_='True')
    18. for item in t_list:
    19. print(item)
    20. #5. text 参数
    21. t_list = bs.find_all(text=['hao123','地图','贴吧'])
    22. t_list = bs.find_all(re.compile("\d")) #应用正则表达式来查找包含特定文本的内容(标签里的字符串)
    23. for item in t_list:
    24. print(item)
    25. # 6.limit
    26. t_list = bs.find_all('a'limit= 3) # 限制搜索返回个数
    27. # 7. css选择器
    28. t_list = bs.select('title')
    29. t_list = bs.select('.mnav') # 按类名查找
    30. t_list = bs.select('#u1') # 按id查找
    31. t_list = bs.select('a[class="bri"]') # 按属性查找
    32. t_list = bs.select("head > title") # 按子标签查找
    33. t_list = bs.select('.mnav~.bri') # 按兄弟节点查找 manv 的bri的 兄弟
    34. pring(t_list[0].get_text())
    35. for item in t_list:
    36. print(item)
  2. 正则表达式

Screen Shot 2021-11-25 at 2.59.24 PM.pngScreen Shot 2021-11-25 at 3.21.03 PM.png
ps:很多用户名要求大小写+_,因为,可以用\w很快匹配

Screen Shot 2021-11-25 at 3.24.44 PM.pngScreen Shot 2021-11-25 at 3.25.39 PM.png

  1. import re
  2. #创建模式对象
  3. pat = re.compile("AA") # 此处的AA,是正则表达式,用来去验证其他的字符串
  4. m = pat.search('CBAABDAACEG') # search字符串被校验的内容. search只返回第一个匹配的
  5. print(m)
  6. m = re.search('asd','Aasd') ## 前:模版 后:被校验的对象
  7. print(m)
  8. print(re.findall("a","ASEAaDNOa"))## 前:模版 后:被校验的对象, 返回list
  9. print(re.findall("[A-Z]","ASEAaDNOa")) # 挑出所有大些字母
  10. print(re.findall("[A-Z]+","ASEAaDNOa"))# 挑出所有大些字母组合
  11. ## sub
  12. print(re.sub("a",'A','abcdliebowb')) # 找到a用A替换,在第三个字符串中替换
  13. ## 建议在正则表达式中,被比较的字符串前面加上r,不用担心转转义字符的问题

爬虫的全过程:

  1. #-*- codeing = utf-8 -*_
  2. #@Time: 2021/11/22
  3. #@Author: yl-5940
  4. #@File : douban.py
  5. #@Software: PyCharm
  6. from bs4 import BeautifulSoup ## 网页解析
  7. import re ## 正则表达式,进行文字匹配
  8. import xlwt ## 进行excel操作
  9. import urllib.request,urllib.error ## 定制URL,获取网页数据
  10. import sqlite3 ## 进行SQLite数据库操作
  11. def main():
  12. baseurl = 'https://movie.douban.com/top250?start='
  13. ##1,2.爬取网页、解析数据
  14. datalist = getData(baseurl)
  15. ##3.保存数据
  16. savepath = '豆瓣电影Top250.xls'
  17. saveData(datalist,savepath)
  18. ## 4.保存到数据库
  19. dbpath = 'movie.db'
  20. saveData2DB(datalist,dbpath)
  21. ## 用正则匹配所需要获取的信息
  22. # 影片详情链接的规则
  23. findLink = re.compile(r'<a href="(.*?)">') # 创建正则表达式对象,表示规则(字符串模式)
  24. # 影片图片
  25. findImgSrc = re.compile(r'<img.*src="(.*?)"',re.S) ## . 不匹配换行符,加上re.S表示忽略换行符,让换行符也包括在内
  26. # 影片片名
  27. findTitle = re.compile(r'<span class="title">(.*)</span>')
  28. # 影片评分
  29. findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>')
  30. # 评价人数
  31. findJudge= re.compile(r'<span>(\d*)人评价</span>')
  32. # 找到概况
  33. findInq = re.compile(r'<span class="inq">(.*)</span>')
  34. # 找到影片相关内容
  35. findBd = re.compile(r'<p class="">(.*?)</p>',re.S)
  36. ## 爬取网页
  37. def getData(baseurl):
  38. datalist = []
  39. for i in range(0,10): ## 调用获取页面信息的函数,10次
  40. url = baseurl + str(i*25)
  41. html =askURL(url) ## 保存获取到的网页源码
  42. ## 2. 逐一解析, 采用BeautifulSoup
  43. soup = BeautifulSoup(html,'html.parser')
  44. for item in soup.find_all('div', class_="item"):## 查找符合要求的字符串,形成列表
  45. #print(item) # 测试,查看电影item的全部信息
  46. data =[]
  47. item = str(item) ## 转换为str来采用正则
  48. # 获取影片的超链接
  49. link = re.findall(findLink,item)[0]
  50. data.append(link)
  51. # 获取影片的图片
  52. imgSrc = re.findall(findImgSrc,item)[0]
  53. data.append(imgSrc)
  54. # 获取影片的名字
  55. titles = re.findall(findTitle, item) ## 片名可能有中英文
  56. if len(titles)==2:
  57. ctitle = titles[0] # 添加中文名
  58. data.append(ctitle)
  59. otitle = titles[1].replace('/','') # 添加外文名
  60. data.append(otitle)
  61. else:
  62. data.append(titles[0])
  63. data.append(' ') ## 外国名留空、占位 很重要!
  64. # 获取影片的评分
  65. rating = re.findall(findRating,item)
  66. data.append(rating)
  67. # 获取影片的评价人数
  68. judgeNum = re.findall(findJudge,item)
  69. data.append(judgeNum)
  70. # 获取影片的一句话评价
  71. inq = re.findall(findInq,item) ## 有的inq可能是空的
  72. if len(inq) !=0 :
  73. inq = inq[0].replace('。','') # 去掉句号
  74. data.append(inq)
  75. else:
  76. data.append(' ') ## 留空
  77. bd = re.findall(findBd,item)[0]
  78. bd = re.sub('<br(\s+)?/>(\s+)?'," ",bd) # 去掉.<br/>
  79. bd = re.sub('/'," ",bd) # 替换 /
  80. data.append(bd.strip()) # 去掉前后空格
  81. datalist.append(data) ## 把处理好的一步电影的新型放入datalist
  82. print(datalist)
  83. return datalist
  84. ## 保存数据
  85. def saveData(datalist,savepath):
  86. book = xlwt.Workbook(encoding='utf-8',style_compression=0) ## 创建workbook对象
  87. sheet = book.add_sheet('豆瓣电影Top250',cell_overwrite_ok=True) ## 创建工作表
  88. col = ("电影详情链接",'图片链接','影片中文名','影片外文名','评分','评价人数','概况','相关信息')
  89. for i in range(0,8):
  90. sheet.write(0,i,col[i]) ## 写入列名
  91. for i in range(0,250):
  92. print("第%d条"%(i+1))
  93. data = datalist[i]
  94. for j in range(0,8):
  95. sheet.write(i+1,j,data[j]) ##
  96. book.save(savepath)
  97. ## 得到指定一个url网页内容
  98. def askURL(url):
  99. head = { ## 模拟浏览器头部信息,向豆瓣服务器发消息
  100. "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"
  101. } ## 用户代理,表示告诉豆瓣服务器,我们是什么类型的机器,浏览器(本质上是告诉浏览器我们可以接受什么格式的文件)
  102. request = urllib.request.Request(url, headers=head)
  103. html = ""
  104. try:
  105. response = urllib.request.urlopen(request)
  106. html = response.read().decode('utf-8')
  107. #print(html)
  108. except urllib.error.URLError as e:
  109. if hasattr(e,'code'):
  110. print(e.code)
  111. if hasattr(e,'reason'):
  112. print(e.reason)
  113. return html
  114. ## 储存到数据库
  115. def saveData2DB(datalist,dbpath):
  116. init_db(dbpath)
  117. conn = sqlite3.connect(dbpath)
  118. cur =conn.cursor()
  119. for data in datalist:
  120. for index in range(len(data)):
  121. #print(type(data[index]))
  122. if type(data[index]) == list:## 评分和评价人数typelist, 不要换成str,否则无法写入
  123. data[index] = data[index][0]
  124. else:
  125. data[index] = '"'+str(data[index]) +'"'
  126. sql = '''
  127. insert into movie250(
  128. info_link,pic_link,cname,ename, score,rated,introduction,info)
  129. values(%s)'''%",".join(data)
  130. cur.execute(sql)
  131. conn.commit()
  132. cur.close()
  133. conn.close()
  134. def init_db(dbpath):
  135. # 创建数据表
  136. sql = '''
  137. create table movie250
  138. (
  139. id integer primary key autoincrement,
  140. info_link text,
  141. pic_link text,
  142. cname varchar,
  143. ename varchar,
  144. score numeric,
  145. rated numeric,
  146. introduction text,
  147. info text
  148. )
  149. '''
  150. conn = sqlite3.connect(dbpath)
  151. cursor = conn.cursor()
  152. cursor.execute(sql)
  153. conn.commit()
  154. conn.close()
  155. if __name__ == "__main__" : ## 当程序执行时
  156. ## 调用函数 ,可以空值函数和代码的主流程
  157. ## 可以用于测试代码,让程序控制在此语句下
  158. main()