01. 豆瓣影评获取

1.1 获取电影数据

1.1.1 获取电影数据的URL

  • 要获取电影数据可以先访问豆瓣电影官网:https://movie.douban.com/,为了减少一点数据量可以点击热门,然后点击更多按钮查看全部热门电影。

image.png

  • 在新接口中,默认显示的电影数量有点少,而勾选可播放后就可以获取比较多的数据。
  • 由此可以推断出,这些电影数据不是一开始就在页面中的,而是当点击某些按钮时,才从服务器中请求过来的动态数据。

image.png

  • 此时可以打开Network中的Fetch/XHR,然后可以点击左上角的clear按钮清空一下已经存在的记录。

image.png

  • 然后将页面下拉到底部,点击“加载更多”按钮去加载更多的电影数据。

image.png

  • 此时浏览器就会抓到一条动态数据。

image.png

  • 查看这条数据的HTTP请求头,即可获取到这条请求的URL。

image.png

1.1.2 用requests模块请求该URL

  • 直接请求这条URL,发现响应状态码是418,因此这里肯定有反扒机制。

    1. import requests
    2. resp = requests.get("https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=20&count=20&selected_categories=%7B%7D&uncollect=false&playable=true&tags=")
    3. print(resp) # <Response [418]>
  • 根据之前学习的经验,这种情况可以优先考虑采用填充请求头的方式来解决。(只填充User-Agent会得到400,再填充Referer即可得到200) ```python import requests

url = “https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=20&count=20&selected_categories=%7B%7D&uncollect=false&playable=true&tags=“ headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46’, ‘Referer’: ‘https://movie.douban.com/explore‘ }

resp = requests.get(url=url, headers=headers) print(resp) #

  1. <a name="XyCwh"></a>
  2. #### 1.1.3 获取响应体
  3. - 可以先用`text`属性查看一下响应体。
  4. ```python
  5. >>> resp.text
  6. '{"count": 20, "show_rating_filter": true, "recommend_categories": [{"is_control": true, "type": "\\u7c7b\\u578b", "data": [{"default": true, "text": "\\u5168\\u90e8\\u7c7b\\u578b"}, {"default": false, "text": "\\u559c\\u5267"}, {"default": false, "text": "\\u7231\\u60c5"},
  • 可以发现这个是一个JSON数据,因此可以用json()函数来接收。
  • 通过简单分析,发现所有的电影数据都在items这个Key中;并且这是一个大列表,每一部电影都是这个列表中的一个元素。

    1. >>> type(resp.json().get("items"))
    2. <class 'list'>
    3. >>> for movie in resp.json().get("items"):
    4. ... print(movie)
    5. ...
    6. {'comment': {'comment': '这么说吧,光想到人类唯一的幸存者是沈腾就感觉很好笑', 'id': '3438260632', 'user': {'kind': 'user', 'name': '刘初柒', 'url': 'https://www.douban.com/people/65880892/', 'uri': 'douban://douban.com/user/65880892', 'avatar': 'https://img2.douba
  • 可以发现,每条电影数据又是一个字典,而为了后续操作,我们只需要获取每部电影的电影名称(key=title)和电影ID(key=id)即可。

    1. >>> for movie in resp.json().get("items"):
    2. ... print(movie.get("title"), movie.get("id"))
    3. ...
    4. 独行月球 35183042
    5. 明日战记 26353671
    6. 流浪地球 26266893
    7. 少年的你 30166972
    8. 哈利·波特与魔法石 1295038
    9. 当幸福来敲门 1849031
    10. 海上钢琴师 1292001
    11. 大话西游之大圣娶亲 1292213
    12. 夏洛特烦恼 25964071
    13. 功夫 1291543
    14. 西虹市首富 27605698
    15. 忠犬八公的故事 3011091
    16. 飞屋环游记 2129039
    17. 唐伯虎点秋香 1306249
    18. 我和我的祖国 32659890
    19. 触不可及 6786002
    20. 龙猫 1291560
    21. 无间道 1307914
    22. 头号玩家 4920389
    23. 唐人街探案3 27619748
  • 代码整合: ```python import requests

url = “https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=20&count=20&selected_categories=%7B%7D&uncollect=false&playable=true&tags=“ headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46’, ‘Referer’: ‘https://movie.douban.com/explore‘ }

resp = requests.get(url=url, headers=headers)

movies_data = resp.json().get(“items”) for movie in movies_data: title = movie.get(“title”) movie_id = movie.get(“id”) print(title, movie_id)

  1. <a name="CUDHi"></a>
  2. #### 1.1.4 获取所有电影数据
  3. - 从1.1.3中可以清楚地看出,爬虫程序获取到的只有20条数据,这显然不是豆瓣电影中的所有电影数据。
  4. - 现在来分析URL:`https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=20&count=20&selected_categories=%7B%7D&uncollect=false&playable=true&tags=`,可以猜测数据条数是由`start=20&count=20`这两个分页参数控制的。
  5. - 尝试将`start`的值改为0,将`count`的值尽可能写大,尝试获取足够多的数据。(这里将`count`设置到2000)
  6. ```python
  7. # 导入模块
  8. >>> import requests
  9. # 定义URL和请求头
  10. >>> url = "https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=0&count=2000&selected_categories=%7B%7D&uncollect=false&playable=true&tags="
  11. >>> headers={
  12. ... 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46',
  13. ... 'Referer': 'https://movie.douban.com/explore'
  14. ... }
  15. # 发起请求,获取电影条数。
  16. >>> resp = requests.get(url=url, headers=headers)
  17. >>> len(resp.json().get("items"))
  18. 500
  • 发现最终电影的条数是500条,因此猜测用https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=0&count=500&selected_categories=%7B%7D&uncollect=false&playable=true&tags=这条URL就可以获取豆瓣电影中所有的电影数据。
  • 代码整合: ```python import requests

url = “https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=0&count=500&selected_categories=%7B%7D&uncollect=false&playable=true&tags=“ headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46’, ‘Referer’: ‘https://movie.douban.com/explore‘ }

resp = requests.get(url=url, headers=headers) movies_data = resp.json().get(“items”)

for index, movie in enumerate(movies_data): title = movie.get(“title”) movie_id = movie.get(“id”) print(index, title, movie_id)

  1. <a name="k0Fnl"></a>
  2. ### 1.2 影评页面
  3. <a name="RZIpW"></a>
  4. #### 1.2.1 影评页面地址分析
  5. - 以人生大事的前三页评论地址为例:
  6. > https://movie.douban.com/subject/35460157/comments?limit=20&status=P&sort=new_score
  7. > https://movie.douban.com/subject/35460157/comments?start=20&limit=20&status=P&sort=new_score
  8. > https://movie.douban.com/subject/35460157/comments?start=40&limit=20&status=P&sort=new_score
  9. - 其中35460157像个ID,因此怀疑是电影的ID。
  10. - 参数:start开始位置;limit显示条数;status状态,一般不用去改;sort排序规则。
  11. - 尝试将start设置为0,将limit尽可能的取最大值。
  12. - 发现一次性可获取的最大评论数量为500,即start=0&limit=500:`https://movie.douban.com/subject/35460157/comments?start=0&limit=500&status=P&sort=new_score`
  13. <a name="nKqbf"></a>
  14. #### 1.2.2 获取所有电影的评价页面
  15. - 既然35460157是一个电影的ID,那么所有电影的评价页面可以总结为:`https://movie.douban.com/subject/{movie_id}/comments?start=0&limit=500&status=P&sort=new_score`
  16. - 在1.1.4中我们已经获取到了所有的电影ID,那么由此也可以获取到所有的评价页面:
  17. ```python
  18. for movie in movies_data:
  19. title = movie.get("title")
  20. movie_id = movie.get("id")
  21. movie_comment_url = f"https://movie.douban.com/subject/{movie_id}/comments?start=0&limit=500&status=P&sort=new_score"
  22. print(title, movie_comment_url)

1.3 获取评论

1.3.1 获取一部电影的一页评论

  • 还是以人生大事为例,获取一页评论只需要将https://movie.douban.com/subject/35460157/comments?start=0&limit=500&status=P&sort=new_score用Selenium打开,然后获取评论数据即可。 ```python from selenium import webdriver from selenium.webdriver.common.by import By

browser = webdriver.Chrome() browser.get(“https://movie.douban.com/subject/35460157/comments?start=0&limit=500&status=P&sort=new_score“)

comments = [comment.text for comment in browser.find_elements(by=By.CLASS_NAME, value=”short”)] print(comments)

  1. <a name="FihfE"></a>
  2. #### 1.3.2 获取一部电影的多页评论
  3. - 获取一部电影的多页评论实际上就是两步操作:获取一页评论、翻页。
  4. - 对于获取一页评论,可以单独定义一个函数出来。这个函数的功能就只是打开一个评论页面,然后将这个页面中所有评论获取出来,然后返回。
  5. - 而翻页操作,我们可能发现豆瓣电影的翻页是有URL中的start参数控制的,当limit为500时,第一页start为0、第二页start为500、第三页start为1500,以此类推。(![](https://cdn.nlark.com/yuque/__latex/62cd0b92c81b4fc676e97a062d0e2a5b.svg#card=math&code=P_%7Bn%2B1%7D%20%3D%20P_n%20%2B%20500&id=iztDs))
  6. ```python
  7. from selenium import webdriver
  8. from selenium.webdriver.common.by import By
  9. def extract_page_comment(url, driver):
  10. """
  11. 用于获取每一页的评论。
  12. :param url: 每一页的链接地址。
  13. :param driver: Selenium的浏览器驱动。
  14. :return: 将获取到的评论以列表的形式返回。
  15. """
  16. driver.get(url)
  17. comments = [comment.text for comment in driver.find_elements(by=By.CLASS_NAME, value="short")]
  18. return comments
  19. browser = webdriver.Chrome()
  20. comments_container = [] # 每一页的评论都会追加到这个容器中。
  21. start_index = 0 # 起始索引为0,每次加500表示翻页。
  22. while True:
  23. # 每页的地址和start_index有关,因此需要重新生成。
  24. base_url = f"https://movie.douban.com/subject/35460157/comments?start={start_index}&limit=500&status=P&sort=new_score"
  25. # 用extract_page_comment()函数获取该页的评论。
  26. comments = extract_page_comment(base_url, browser)
  27. # 当前页的评论获取操作结束后。
  28. # 若当前页的评论为空,则说明这部电影的评论已经获取结束,可退出循环。
  29. # 否则还需要继续获取下一页的内容(将当前页的内容追加到容器中,并让索引向后移动500)。
  30. if comments:
  31. comments_container.extend(comments)
  32. start_index += 500
  33. else:
  34. break
  35. print(comments_container)

1.3.3 获取多部电影的多部评论

  • 获取多部电影的多页评论实际上就是将1.1.4与1.3.2的内容进行一个整合即可。 ```python from selenium import webdriver from selenium.webdriver.common.by import By

import requests

def extract_page_comment(url, driver): “”” 用于获取每一页的评论。 :param url: 每一页的链接地址。 :param driver: Selenium的浏览器驱动。 :return: 将获取到的评论以列表的形式返回。 “”” driver.get(url) comments = [comment.text for comment in driver.find_elements(by=By.CLASS_NAME, value=”short”)] return comments

url = “https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=0&count=500&selected_categories=%7B%7D&uncollect=false&playable=true&tags=“ headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46’, ‘Referer’: ‘https://movie.douban.com/explore‘ }

resp = requests.get(url=url, headers=headers) movies_data = resp.json().get(“items”) browser = webdriver.Chrome()

for movie in movies_data: title = movie.get(“title”) movie_id = movie.get(“id”) comments_container = [] start_index = 0

  1. while True:
  2. # 每页的地址和start_index有关,因此需要重新生成。
  3. base_url = f"https://movie.douban.com/subject/{movie_id}/comments?start={start_index}&limit=500&status=P&sort=new_score"
  4. # 用extract_page_comment()函数获取该页的评论。
  5. comments = extract_page_comment(base_url, browser)
  6. # 当前页的评论获取操作结束后。
  7. # 若当前页的评论为空,则说明这部电影的评论已经获取结束,可退出循环。
  8. # 否则还需要继续获取下一页的内容(将当前页的内容追加到容器中,并让索引向后移动500)。
  9. if comments:
  10. comments_container.extend(comments)
  11. start_index += 500
  12. else:
  13. break
  14. print(title, comments_container)
  1. <a name="JAwLh"></a>
  2. ### 1.4 数据持久化
  3. - 由于爬取到的评论都是非关系型的文本数据,因此可以使用最简单的txt文本文档来存储。
  4. ```python
  5. import os
  6. # 其他模块
  7. try:
  8. os.mkdir("豆瓣影评")
  9. except Exception as e:
  10. pass
  11. # 循环外的其他代码
  12. for movie in movies_data:
  13. # 评论获取部分的代码
  14. with open(f"豆瓣影评/{title}.txt", 'w', encoding='utf-8') as file:
  15. for comment in comments_container:
  16. file.write(f"{comment}\n")
  17. print(f"《{title}》影评爬取完成!")

1.5 项目代码整合

  1. import random
  2. import time
  3. import requests
  4. import os
  5. from selenium import webdriver
  6. from selenium.webdriver.common.by import By
  7. def extract_page_comment(url, driver):
  8. """
  9. 用于获取每一页的评论。
  10. :param url: 每一页的链接地址。
  11. :param driver: Selenium的浏览器驱动。
  12. :return: 将获取到的评论以列表的形式返回。
  13. """
  14. driver.get(url)
  15. comments = [comment.text for comment in driver.find_elements(by=By.CLASS_NAME, value="short")]
  16. return comments
  17. url = "https://m.douban.com/rexxar/api/v2/movie/recommend?refresh=0&start=0&count=500&selected_categories=%7B%7D&uncollect=false&playable=true&tags="
  18. headers={
  19. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46',
  20. 'Referer': 'https://movie.douban.com/explore'
  21. }
  22. resp = requests.get(url=url, headers=headers)
  23. movies_data = resp.json().get("items")
  24. browser = webdriver.Chrome()
  25. try:
  26. os.mkdir("豆瓣影评")
  27. except Exception as e:
  28. pass
  29. for movie in movies_data:
  30. title = movie.get("title")
  31. movie_id = movie.get("id")
  32. comments_container = []
  33. start_index = 0
  34. while True:
  35. # 每次请求前随机休眠2~5秒,避免访问频繁。
  36. time.sleep(random.randint(2, 5))
  37. # 每页的地址和start_index有关,因此需要重新生成。
  38. base_url = f"https://movie.douban.com/subject/{movie_id}/comments?start={start_index}&limit=500&status=P&sort=new_score"
  39. # 用extract_page_comment()函数获取该页的评论。
  40. comments = extract_page_comment(base_url, browser)
  41. # 当前页的评论获取操作结束后。
  42. # 若当前页的评论为空,则说明这部电影的评论已经获取结束,可退出循环。
  43. # 否则还需要继续获取下一页的内容(将当前页的内容追加到容器中,并让索引向后移动500)。
  44. if comments:
  45. comments_container.extend(comments)
  46. start_index += 500
  47. else:
  48. break
  49. with open(f"豆瓣影评/{title}.txt", 'w', encoding='utf-8') as file:
  50. for comment in comments_container:
  51. file.write(f"{comment}\n")
  52. print(f"《{title}》影评爬取完成!")

02. 链家房产信息获取

2.1 项目需求

  • 访问链家首页:https://bj.lianjia.com/
  • 然后输入一个指定的区域,接着获取这个区域内的房源数据。
  • 获取房源信息中的:城区、小区名称、房屋户型、建筑面积、套内面积、单价、总价、房屋朝向、装修情况、配备电梯、供暖方式等数据信息。

    2.2 需求分析与实现

    2.2.1 获取房源数据

  • 获取房源数据用浏览器实现:

    • 浏览器打开链家首页:https://bj.lianjia.com/
    • 然后在搜索框内输入区域名称,接着点击开始找房按钮。

image.png

  • 此时浏览器就会返回该区域内的各个房源信息。

image.png

  • 这个过程可以用Selenium程序轻松实现: ```sql from selenium import webdriver from selenium.webdriver.common.by import By

import time

打开浏览器,访问链家首页

browser = webdriver.Chrome() browser.get(“https://bj.lianjia.com/“) time.sleep(2)

找到搜索框,输入北京,并点击开始找房。

search_input = browser.find_element(by=By.CSS_SELECTOR, value=”#keyword-box”) search_input.send_keys(“北京”) find_btn = browser.find_element(by=By.CSS_SELECTOR, value=”#findHouse”) find_btn.click() time.sleep(3)

  1. <a name="dPySc"></a>
  2. #### 2.2.2 进入房源详情页获取详细信息
  3. - 进入详情页获取详细信息用浏览器实现:
  4. - 点击每个房源信息的标题(里面含有一个超链接),即可跳转到该房源对应的详情页。
  5. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2692415/1670468862066-20af0795-92a1-4f57-89c5-1e906e661d82.png#averageHue=%23f3efec&clientId=u58809aa1-37be-4&from=paste&height=170&id=u1070e602&originHeight=216&originWidth=955&originalType=binary&ratio=1&rotation=0&showTitle=false&size=79201&status=done&style=none&taskId=ud23418a7-c99e-4033-a4c9-c016752f35f&title=&width=750)
  6. - 详情页中有该房源的详细说明信息,值得注意的是该操作浏览器会打开一个新窗口。
  7. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2692415/1670469059523-c1b45d2e-6346-4f3f-84c7-3c278ab8e2a3.png#averageHue=%23d3cfbe&clientId=u58809aa1-37be-4&from=paste&height=581&id=u8d00e3a3&originHeight=1020&originWidth=1316&originalType=binary&ratio=1&rotation=0&showTitle=false&size=401335&status=done&style=none&taskId=u14961fea-4b13-4b1d-a267-871274eb1ee&title=&width=750)
  8. - 信息位置:
  9. ![城区 & 小区名称](https://cdn.nlark.com/yuque/0/2022/png/2692415/1670474426201-3c5bc61f-8fcb-4a2a-936c-8b2bb6b56a3f.png#averageHue=%23f9f3f2&clientId=ufa42099f-ac19-4&from=paste&height=225&id=u90f018ac&originHeight=228&originWidth=456&originalType=binary&ratio=1&rotation=0&showTitle=true&size=26039&status=done&style=none&taskId=u0fd14d39-9189-4457-92bd-87bf551a0b4&title=%E5%9F%8E%E5%8C%BA%20%26%20%E5%B0%8F%E5%8C%BA%E5%90%8D%E7%A7%B0&width=450 "城区 & 小区名称")<br />![房屋户型 & 建筑面积 & 套内面积 & 房屋朝向 & 装修情况 & 配备电梯 & 供暖方式](https://cdn.nlark.com/yuque/0/2022/png/2692415/1670474683673-d77a416c-a2d4-4b31-a31f-c19fa38342ef.png#averageHue=%23fdf7f7&clientId=ufa42099f-ac19-4&from=paste&height=246&id=uec3b89d8&originHeight=252&originWidth=667&originalType=binary&ratio=1&rotation=0&showTitle=true&size=21466&status=done&style=none&taskId=uecc66cce-91c5-4e68-a53b-1d3d80ffe88&title=%E6%88%BF%E5%B1%8B%E6%88%B7%E5%9E%8B%20%26%20%E5%BB%BA%E7%AD%91%E9%9D%A2%E7%A7%AF%20%26%20%E5%A5%97%E5%86%85%E9%9D%A2%E7%A7%AF%20%26%20%E6%88%BF%E5%B1%8B%E6%9C%9D%E5%90%91%20%26%20%E8%A3%85%E4%BF%AE%E6%83%85%E5%86%B5%20%26%20%E9%85%8D%E5%A4%87%E7%94%B5%E6%A2%AF%20%26%20%E4%BE%9B%E6%9A%96%E6%96%B9%E5%BC%8F&width=650 "房屋户型 & 建筑面积 & 套内面积 & 房屋朝向 & 装修情况 & 配备电梯 & 供暖方式")<br />![单价 & 总价](https://cdn.nlark.com/yuque/0/2022/png/2692415/1670503120677-d141cfcf-98d1-4970-9067-77df0eff26fb.png#averageHue=%23feeceb&clientId=u51871a9a-b52c-4&from=paste&height=104&id=ue502eac8&originHeight=99&originWidth=332&originalType=binary&ratio=1&rotation=0&showTitle=true&size=8912&status=done&style=none&taskId=ued7d0c64-0460-4907-860e-277e72af832&title=%E5%8D%95%E4%BB%B7%20%26%20%E6%80%BB%E4%BB%B7&width=350 "单价 & 总价")
  10. - 当这页数据获取完成后,需要关闭当前窗口,并点击下一个页面继续以上操作。
  11. - 代码实现:
  12. ```python
  13. # 获取所有房子的详情页连接,并点击进入详情页获取详细信息。
  14. house_list = browser.find_elements(
  15. by=By.CSS_SELECTOR,
  16. value=".sellListContent > li > .info > .title > a"
  17. )
  18. for house_li in house_list:
  19. # 点击进入详情页。
  20. house_li.click()
  21. # 切换窗口
  22. browser.switch_to.window(browser.window_handles[-1])
  23. time.sleep(3)
  24. # 城区。
  25. area = browser.find_element(by=By.CSS_SELECTOR, value=".areaName .info a").text
  26. print(area)
  27. # 小区名称。
  28. community_name = browser.find_element(by=By.CSS_SELECTOR, value=".communityName .info").text
  29. print(community_name)
  30. # 获取户型信息。
  31. # 通过观察发现,这些信息都在.box-l .introContent .content > ul下的奇数li中。
  32. house_intro_lis = browser.find_elements(
  33. by=By.CSS_SELECTOR,
  34. value=".box-l .introContent .content > ul > li:nth-child(odd)"
  35. )
  36. # get_attribute("innerHTML")获取到的数据是<span class="label">房屋户型</span>"3室1厅1厨1卫"这样的数据。
  37. # 因此可以根据</span>将字符串切割成两半,第二部分"3室1厅1厨1卫"就是需要的数据。
  38. # 因为需要的只有6个数据,所以可以用切片的方式过滤掉多余的数据。
  39. """
  40. innerHTML的作用也是获取标签内的信息,与text类似,但存在以下差别。
  41. innerHTML:不会忽略标签内容,因此提取出来的是<span class="label">房屋户型</span>3室1厅1厨1卫这样的数据。
  42. text:会忽略标签内容,因此提取出来的是“房屋户型3室1厅1厨1卫”这样的数据。
  43. """
  44. house_intro_contents = [li.get_attribute("innerHTML").split("</span>")[-1] for li in house_intro_lis][:6]
  45. # 接着,为了方便后续处理,可以将列表中的6个数据分别赋值给6个变量。
  46. house_type, house_area, house_true_area, house_direction, house_finish, house_hot = house_intro_contents
  47. print(house_type, house_area, house_true_area, house_direction, house_finish, house_hot, sep="\n")
  48. # 配备电梯。
  49. house_lift = browser.find_element(
  50. by=By.CSS_SELECTOR,
  51. value=".box-l .introContent .content > ul > li:last-child"
  52. ).text.lstrip("配备电梯")
  53. print(house_lift)
  54. # 单价。
  55. house_price = browser.find_element(by=By.CSS_SELECTOR, value=".unitPriceValue").text.rstrip("元/平米")
  56. print(house_price)
  57. # 总价。
  58. house_total_price = browser.find_element(by=By.CSS_SELECTOR, value=".price-container .total").text
  59. print(house_total_price)
  60. # 为了区别于其他房源的数据,因此可以打印一行空行。
  61. print()
  62. # 数据提取完成后,关闭当前窗口
  63. browser.close() # 关闭当前窗口。
  64. browser.switch_to.window(browser.window_handles[0]) # 切换回起始页面。
  65. time.sleep(2)

2.2.3 获取多页数据

  • 获取多页数据用浏览器实现:
    • 当数据过多时网站一般都会选择分页显示,而当一页信息查看完后,可以点击“下一页”按钮浏览下一页数据。

image.png

  • 而当浏览到最后一页时,页面中就不会出现“下一页”按钮了,此时就说明所有页面信息已经全部浏览完成。

image.png

  • 存在问题:
    • 问题描述:在使用next_btn.click()点击“下一页”按钮时,Python程序报错提示无法点击。
    • 解决方式:换成JavaScript代码执行点击操作,然后由Selenium程序发送执行。
  • 代码实现:

    1. # 在获取当前分页数据的外面套一层循环,用来控制翻页。
    2. # 翻页要翻多少次并不知道,只知道当翻页按钮的最后一个不是下一页时,就停止。
    3. page = 1 # 用于记录当前页码。
    4. while True:
    5. print(f"正在获取第{page}页的数据。。。")
    6. page += 1
    7. # 获取所有房子的详情页连接,并点击进入详情页获取详细信息。
    8. house_list = browser.find_elements(by=By.CSS_SELECTOR, value=".sellListContent > li > .info > .title > a")
    9. for house_li in house_list:
    10. # 点击进入详情页。
    11. browser.execute_script('$(arguments[0].click())', house_li) # 这段代码的含义在下面翻页操作中有详细说明。
    12. # 切换窗口
    13. browser.switch_to.window(browser.window_handles[-1])
    14. time.sleep(3)
    15. # 城区。
    16. area = browser.find_element(by=By.CSS_SELECTOR, value=".areaName .info a").text
    17. print(area)
    18. # 小区名称。
    19. community_name = browser.find_element(by=By.CSS_SELECTOR, value=".communityName .info").text
    20. print(community_name)
    21. # 获取户型信息。
    22. # 通过观察发现,这些信息都在.box-l .introContent .content > ul下的奇数li中。
    23. house_intro_lis = browser.find_elements(by=By.CSS_SELECTOR, value=".box-l .introContent .content > ul > li:nth-child(odd)")
    24. # get_attribute("innerHTML")获取到的数据是<span class="label">房屋户型</span>"3室1厅1厨1卫"这样的数据。
    25. # 因此可以根据</span>将字符串切割成两半,第二部分"3室1厅1厨1卫"就是需要的数据。
    26. # 因为需要的只有6个数据,所以可以用切片的方式过滤掉多余的数据。
    27. """
    28. innerHTML的作用也是获取标签内的信息,与text类似,但存在以下差别。
    29. innerHTML:不会忽略标签内容,因此提取出来的是<span class="label">房屋户型</span>3室1厅1厨1卫这样的数据。
    30. text:会忽略标签内容,因此提取出来的是“房屋户型3室1厅1厨1卫”这样的数据。
    31. """
    32. house_intro_contents = [li.get_attribute("innerHTML").split("</span>")[-1] for li in house_intro_lis][:6]
    33. # 接着,为了方便后续处理,可以将列表中的6个数据分别赋值给6个变量。
    34. house_type, house_area, house_true_area, house_direction, house_finish, house_hot = house_intro_contents
    35. print(house_type, house_area, house_true_area, house_direction, house_finish, house_hot, sep="\n")
    36. # 配备电梯。
    37. house_lift = browser.find_element(by=By.CSS_SELECTOR, value=".box-l .introContent .content > ul > li:last-child").text.lstrip("配备电梯")
    38. print(house_lift)
    39. # 单价。
    40. house_price = browser.find_element(by=By.CSS_SELECTOR, value=".unitPriceValue").text.rstrip("元/平米")
    41. print(house_price)
    42. # 总价。
    43. house_total_price = browser.find_element(by=By.CSS_SELECTOR, value=".price-container .total").text
    44. print(house_total_price)
    45. # 为了区别于其他房源的数据,因此可以打印一行空行。
    46. print()
    47. # 数据提取完成后,关闭当前窗口
    48. browser.close() # 关闭当前窗口。
    49. browser.switch_to.window(browser.window_handles[0]) # 切换回起始页面。
    50. time.sleep(2)
    51. # 当翻页按钮的最后一个是下一页时,点击下一页。
    52. # 当翻页按钮的最后一个不是下一页时,停止循环。
    53. next_btn = browser.find_element(by=By.CSS_SELECTOR, value=".house-lst-page-box > a:last-child")
    54. if next_btn.text == '下一页':
    55. # 因为next_btn.click()会报无法点击的异常,因此这里只能用JavaScript的代码来实现点击操作。
    56. """
    57. Selenium中通过browser.execute_script(JS代码, 需要传递的参数)来执行JS代码。
    58. $(arguments[0].click())是一段JS代码,arguments[0]表示传递的第0个参数,即next_btn。
    59. 接着对next_btn进行点击操作,故这段代码本质上与next_btn.click()是一样的,只不过换了一个方式去实现这个功能而已。
    60. """
    61. browser.execute_script('$(arguments[0].click())', next_btn)
    62. time.sleep(5)
    63. else:
    64. break

    2.2.4 数据持久化

  • 数据爬取完成后,可以将爬取的数据存储到Excel文件中。 ```python import openpyxl

save_datas = [ [‘城区’, ‘小区名称’, ‘房屋户型’, ‘建筑面积’, ‘套内面积’, ‘房屋朝向’, ‘装修情况’, ‘供暖方式’, ‘配备电梯’, ‘单价(元/平米)’, ‘总价(万元)’] ]

在获取当前分页数据的外面套一层循环,用来控制翻页。

翻页要翻多少次并不知道,只知道当翻页按钮的最后一个不是下一页时,就停止。

page = 1 # 用于记录当前页码。 while True: print(f”正在获取第{page}页的数据。。。”) page += 1

  1. # 获取当前页所有房子的详情页连接,并点击进入详情页获取详细信息。
  2. house_list = browser.find_elements(by=By.CSS_SELECTOR, value=".sellListContent > li > .info > .title > a")
  3. for house_li in house_list:
  4. # 点击进入详情页。
  5. browser.execute_script('$(arguments[0].click())', house_li) # 这段代码的含义在下面翻页操作中有详细说明。
  6. # 切换窗口
  7. browser.switch_to.window(browser.window_handles[-1])
  8. time.sleep(3)
  9. area = browser.find_element(by=By.CSS_SELECTOR, value=".areaName .info a").text # 城区。
  10. community_name = browser.find_element(by=By.CSS_SELECTOR, value=".communityName .info").text # 小区名称。
  11. # 获取户型信息。
  12. # 通过观察发现,这些信息都在.box-l .introContent .content > ul下的奇数li中。
  13. house_intro_lis = browser.find_elements(by=By.CSS_SELECTOR, value=".box-l .introContent .content > ul > li:nth-child(odd)")
  14. # get_attribute("innerHTML")获取到的数据是<span class="label">房屋户型</span>"3室1厅1厨1卫"这样的数据。
  15. # 因此可以根据</span>将字符串切割成两半,第二部分"3室1厅1厨1卫"就是需要的数据。
  16. # 因为需要的只有6个数据,所以可以用切片的方式过滤掉多余的数据。
  17. """
  18. innerHTML的作用也是获取标签内的信息,与text类似,但存在以下差别。
  19. innerHTML:不会忽略标签内容,因此提取出来的是<span class="label">房屋户型</span>3室1厅1厨1卫这样的数据。
  20. text:会忽略标签内容,因此提取出来的是“房屋户型3室1厅1厨1卫”这样的数据。
  21. """
  22. house_intro_contents = [li.get_attribute("innerHTML").split("</span>")[-1] for li in house_intro_lis][:6]
  23. # 接着,为了方便后续处理,可以将列表中的6个数据分别赋值给6个变量。
  24. house_type, house_area, house_true_area, house_direction, house_finish, house_hot = house_intro_contents
  25. house_lift = browser.find_element(by=By.CSS_SELECTOR, value=".box-l .introContent .content > ul > li:last-child").text.lstrip("配备电梯") # 配备电梯。
  26. house_price = browser.find_element(by=By.CSS_SELECTOR, value=".unitPriceValue").text.rstrip("元/平米") # 单价。
  27. house_total_price = browser.find_element(by=By.CSS_SELECTOR, value=".price-container .total").text # 总价。
  28. # 将一条数据存储到总数据中,方便后续持久化操作。
  29. row_data = [area, community_name, house_type, house_area, house_true_area, house_direction, house_finish, house_hot, house_lift, house_price, house_total_price]
  30. save_datas.append(row_data)
  31. print(row_data)
  32. # 数据提取完成后,关闭当前窗口
  33. browser.close() # 关闭当前窗口。
  34. browser.switch_to.window(browser.window_handles[0]) # 切换回起始页面。
  35. time.sleep(2)
  36. # 当翻页按钮的最后一个是下一页时,点击下一页。
  37. # 当翻页按钮的最后一个不是下一页时,停止循环。
  38. next_btn = browser.find_element(by=By.CSS_SELECTOR, value=".house-lst-page-box > a:last-child")
  39. if next_btn.text == '下一页':
  40. # 因为next_btn.click()会报无法点击的异常,因此这里只能用JavaScript的代码来实现点击操作。
  41. """
  42. Selenium中通过browser.execute_script(JS代码, 需要传递的参数)来执行JS代码。
  43. $(arguments[0].click())是一段JS代码,arguments[0]表示传递的第0个参数,即next_btn。
  44. 接着对next_btn进行点击操作,故这段代码本质上与next_btn.click()是一样的,只不过换了一个方式去实现这个功能而已。
  45. """
  46. browser.execute_script('$(arguments[0].click())', next_btn)
  47. time.sleep(5)
  48. else:
  49. break

数据持久化到Excel文件中。

wb = openpyxl.Workbook() ws = wb.worksheets[0] for line, line_data in enumerate(save_datas): for column, data in enumerate(line_data): ws.cell(line + 1, column + 1, data) wb.save(“./excel/链家.xlsx”)

  1. <a name="uOBoi"></a>
  2. #### 2.2.5 项目代码整合
  3. ```python
  4. from selenium import webdriver
  5. from selenium.webdriver.common.by import By
  6. import time
  7. import openpyxl
  8. save_datas = [
  9. ['城区', '小区名称', '房屋户型', '建筑面积', '套内面积', '房屋朝向', '装修情况', '供暖方式', '配备电梯', '单价(元/平米)', '总价(万元)']
  10. ]
  11. # 打开浏览器,访问链家首页
  12. browser = webdriver.Chrome()
  13. browser.get("https://bj.lianjia.com/")
  14. time.sleep(2)
  15. # 找到搜索框,输入北京,并点击开始找房。
  16. search_input = browser.find_element(by=By.CSS_SELECTOR, value="#keyword-box")
  17. search_input.send_keys("北京")
  18. find_btn = browser.find_element(by=By.CSS_SELECTOR, value="#findHouse")
  19. find_btn.click()
  20. time.sleep(3)
  21. # 在获取当前分页数据的外面套一层循环,用来控制翻页。
  22. # 翻页要翻多少次并不知道,只知道当翻页按钮的最后一个不是下一页时,就停止。
  23. page = 1 # 用于记录当前页码。
  24. while True:
  25. print(f"正在获取第{page}页的数据。。。")
  26. page += 1
  27. # 获取当前页所有房子的详情页连接,并点击进入详情页获取详细信息。
  28. house_list = browser.find_elements(by=By.CSS_SELECTOR, value=".sellListContent > li > .info > .title > a")
  29. for house_li in house_list:
  30. # 点击进入详情页。
  31. browser.execute_script('$(arguments[0].click())', house_li) # 这段代码的含义在下面翻页操作中有详细说明。
  32. # 切换窗口
  33. browser.switch_to.window(browser.window_handles[-1])
  34. time.sleep(3)
  35. area = browser.find_element(by=By.CSS_SELECTOR, value=".areaName .info a").text # 城区。
  36. community_name = browser.find_element(by=By.CSS_SELECTOR, value=".communityName .info").text # 小区名称。
  37. # 获取户型信息。
  38. # 通过观察发现,这些信息都在.box-l .introContent .content > ul下的奇数li中。
  39. house_intro_lis = browser.find_elements(by=By.CSS_SELECTOR, value=".box-l .introContent .content > ul > li:nth-child(odd)")
  40. # get_attribute("innerHTML")获取到的数据是<span class="label">房屋户型</span>"3室1厅1厨1卫"这样的数据。
  41. # 因此可以根据</span>将字符串切割成两半,第二部分"3室1厅1厨1卫"就是需要的数据。
  42. # 因为需要的只有6个数据,所以可以用切片的方式过滤掉多余的数据。
  43. """
  44. innerHTML的作用也是获取标签内的信息,与text类似,但存在以下差别。
  45. innerHTML:不会忽略标签内容,因此提取出来的是<span class="label">房屋户型</span>3室1厅1厨1卫这样的数据。
  46. text:会忽略标签内容,因此提取出来的是“房屋户型3室1厅1厨1卫”这样的数据。
  47. """
  48. house_intro_contents = [li.get_attribute("innerHTML").split("</span>")[-1] for li in house_intro_lis][:6]
  49. # 接着,为了方便后续处理,可以将列表中的6个数据分别赋值给6个变量。
  50. house_type, house_area, house_true_area, house_direction, house_finish, house_hot = house_intro_contents
  51. house_lift = browser.find_element(by=By.CSS_SELECTOR, value=".box-l .introContent .content > ul > li:last-child").text.lstrip("配备电梯") # 配备电梯。
  52. house_price = browser.find_element(by=By.CSS_SELECTOR, value=".unitPriceValue").text.rstrip("元/平米") # 单价。
  53. house_total_price = browser.find_element(by=By.CSS_SELECTOR, value=".price-container .total").text # 总价。
  54. # 将一条数据存储到总数据中,方便后续持久化操作。
  55. row_data = [area, community_name, house_type, house_area, house_true_area, house_direction, house_finish, house_hot, house_lift, house_price, house_total_price]
  56. save_datas.append(row_data)
  57. print(row_data)
  58. # 数据提取完成后,关闭当前窗口
  59. browser.close() # 关闭当前窗口。
  60. browser.switch_to.window(browser.window_handles[0]) # 切换回起始页面。
  61. time.sleep(2)
  62. # 当翻页按钮的最后一个是下一页时,点击下一页。
  63. # 当翻页按钮的最后一个不是下一页时,停止循环。
  64. next_btn = browser.find_element(by=By.CSS_SELECTOR, value=".house-lst-page-box > a:last-child")
  65. if next_btn.text == '下一页':
  66. # 因为next_btn.click()会报无法点击的异常,因此这里只能用JavaScript的代码来实现点击操作。
  67. """
  68. Selenium中通过browser.execute_script(JS代码, 需要传递的参数)来执行JS代码。
  69. $(arguments[0].click())是一段JS代码,arguments[0]表示传递的第0个参数,即next_btn。
  70. 接着对next_btn进行点击操作,故这段代码本质上与next_btn.click()是一样的,只不过换了一个方式去实现这个功能而已。
  71. """
  72. browser.execute_script('$(arguments[0].click())', next_btn)
  73. time.sleep(5)
  74. else:
  75. break
  76. # 数据持久化到Excel文件中。
  77. wb = openpyxl.Workbook()
  78. ws = wb.worksheets[0]
  79. for line, line_data in enumerate(save_datas):
  80. for column, data in enumerate(line_data):
  81. ws.cell(line + 1, column + 1, data)
  82. wb.save("./excel/链家.xlsx")