Python 爬虫入门教程: 27270 图片爬取
今天继续爬取一个网站, https://www.27270.com/ent/meinvtupian / 这个网站具备反爬, so 我们下载的代码有些地方处理的也不是很到位, 大家重点学习思路, 有啥建议可以在评论的地方跟我说说.
为了以后的网络请求操作方向, 我们这次简单的进行一些代码的封装操作.
在这里你可以先去安装一个叫做 retrying 的模块
pip install retrying
这个模块的具体使用, 自己去百度吧. 嘿嘿哒~
在这里我使用了一个随机产生 user_agent 的方法
- import requests
- from retrying import retry
- import random
- import datetime
- class R:
- def __init__(self,method="get",params=None,headers=None,cookies=None):
- # do something
- def get_headers(self):
- user_agent_list = [ \
- "Mozilla/5.0 (Windows NT 6.1; WOW64) ApplewebKit/537.1 (Khtml, like Gecko) Chrome/22.0.1207.1 Safari/537.1" \
- "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", \
- "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", \
- "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", \
- "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", \
- "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", \
- "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", \
- "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \
- "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \
- "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \
- "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \
- "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \
- "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \
- "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \
- "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", \
- "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", \
- "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
- ]
- UserAgent = random.choice(user_agent_list)
- headers = {'User-Agent': UserAgent}
- return headers
- #other code
retrying 最简单的使用就是给你想不断重试的方法加上 装饰器 @retry
在这里, 我希望网络请求模块尝试 3 次之后, 在报错!
同时在 R 类初始化方法中增加一些必备的参数, 你可以直接看下面的代码
__retrying_requests 方法为私有方法, 其中根据 get 和 post 方式进行逻辑判断
- import requests
- from retrying import retry
- import random
- import datetime
- class R:
- def __init__(self,method="get",params=None,headers=None,cookies=None):
- #do something
- def get_headers(self):
- # do something
- @retry(stop_max_attempt_number=3)
- def __retrying_requests(self,url):
- if self.__method == "get":
- response = requests.get(url,headers=self.__headers,cookies=self.__cookies,timeout=3)
- else:
- response = requests.post(url,params=self.__params,headers=self.__headers,cookies=self.__cookies,timeout=3)
- return response.content
- # other code
网络请求的方法已经声明完毕, 并且返回 response.content 数据流
下面基于这个私有方法, 增加一个获取网络文本的方法和一个获取网络文件的方法. 同步完善类的初始化方法, 在开发中发现, 我们要爬取的网页编码是 gb2312 所以还需要给某些方法增加一个编码参数
- import requests
- from retrying import retry
- import random
- import datetime
- class R:
- # 类的初始化方法
- def __init__(self,method="get",params=None,headers=None,cookies=None):
- self.__method = method
- myheaders = self.get_headers()
- if headers is not None:
- myheaders.update(headers)
- self.__headers = myheaders
- self.__cookies = cookies
- self.__params = params
- def get_headers(self):
- # do something
- @retry(stop_max_attempt_number=3)
- def __retrying_requests(self,url):
- # do something
- # get 请求
- def get_content(self,url,charset="utf-8"):
- try:
- html_str = self.__retrying_requests(url).decode(charset)
- except:
- html_str = None
- return html_str
- def get_file(self,file_url):
- try:
- file = self.__retrying_requests(file_url)
- except:
- file = None
- return file
- # 小编整理一套 Python 资料和 PDF, 有需要 Python 学习资料可以加学习群: 1004391443, 反正闲着也是闲着呢, 不如学点东西啦~~
到此, 这个 R 类已经被我们完善了, 完整的代码, 你应该从上面拼凑起来, 你也可以直接翻到文章最后面, 去 GitHub 上直接查阅.
接下来, 就是比较重要的爬虫代码部分了. 这一次, 我们可以简单的使用一下类和对象, 并且加上简单的多线程操作.
首先, 创建一个 ImageList 类, 这个类第一件事情, 需要获取我们爬取页面的总页码数目
这个步骤比较简单
获取网页源码
正则匹配末页元素
提取数字
- import http_help as hh # 这个 http_help 是我上面写到的那个 R 类
- import re
- import threading
- import time
- import os
- import requests
- # 获取所有待爬取的 URL 列表
- class ImageList():
- def __init__(self):
- self.__start = "https://www.27270.com/ent/meinvtupian/list_11_{}.html" # URL 模板
- # 头文件
- self.__headers = {"Referer":"https://www.27270.com/ent/meinvtupian/",
- "Host":"www.27270.com"
- }
- self.__res = hh.R(headers=self.__headers) # 初始化访问请求
- def run(self):
- page_count = int(self.get_page_count())
- if page_count==0:
- return
- urls = [self.__start.format(i) for i in range(1,page_count)]
- return urls
- # 正则表达式匹配末页, 分析页码
- def get_page_count(self):
- # 注意这个地方需要传入编码
- content = self.__res.get_content(self.__start.format("1"),"gb2312")
- pattern = re.compile("末页")
- search_text = pattern.search(content)
- if search_text is not None:
- count = search_text.group(1)
- return count
- else:
- return 0
- if __name__ == '__main__':
- img = ImageList()
- urls = img.run()
上面的代码注意 get_page_count 方法, 该方法已经获取到了末尾的页码
我们在 run 方法内部, 通过一个列表生成器
urls = [self.__start.format(i) for i in range(1,page_count)]
批量把要爬取的所有链接都生成完毕.
27270 图片 ---- 分析上面爬取到的 URL 列表, 捕获详情页
我们采用生产者和消费者模型, 就是一个抓取链接图片, 一个下载图片, 采用多线程的方式进行操作, 需要首先引入
- import threading
- import time
完整代码如下
- import http_help as hh
- import re
- import threading
- import time
- import os
- import requests
- urls_lock = threading.Lock() #url 操作锁
- imgs_lock = threading.Lock() #图片操作锁
- imgs_start_urls = []
- class Product(threading.Thread):
- # 类的初始化方法
- def __init__(self,urls):
- threading.Thread.__init__(self)
- self.__urls = urls
- self.__headers = {"Referer":"https://www.27270.com/ent/meinvtupian/",
- "Host":"www.27270.com"
- }
- self.__res = hh.R(headers=self.__headers)
- # 链接抓取失败之后重新加入 urls 列表中
- def add_fail_url(self,url):
- print("{} 该 URL 抓取失败".format(url))
- global urls_lock
- if urls_lock.acquire():
- self.__urls.insert(0, url)
- urls_lock.release() # 解锁
- # 线程主要方法
- def run(self):
- print("*"*100)
- while True:
- global urls_lock,imgs_start_urls
- if len(self.__urls)>0:
- if urls_lock.acquire(): # 锁定
- last_url = self.__urls.pop() # 获取 urls 里面最后一个 url, 并且删除
- urls_lock.release() # 解锁
- print("正在操作 {}".format(last_url))
- content = self.__res.get_content(last_url,"gb2312") # 页面注意编码是 gb2312 其他格式报错
- if content is not None:
- HTML = self.get_page_list(content)
- if len(HTML) == 0:
- self.add_fail_url(last_url)
- else:
- if imgs_lock.acquire():
- imgs_start_urls.extend(HTML) # 爬取到图片之后, 把他放在待下载的图片列表里面
- imgs_lock.release()
- time.sleep(5)
- else:
- self.add_fail_url(last_url)
- else:
- print("所有链接已经运行完毕")
- break
- def get_page_list(self,content):
- # 正则表达式
- pattern = re.compile('.*?')
- list_page = re.findall(pattern, content)
- return list_page
上述代码中比较重要的有
到现在为止, 我们已经抓取到了所有的图片地址, 我把他存放在了一个全局的变量里面 imgs_start_urls
这个列表里面存放的是 https://www.27270.com/ent/meinvtupian/2018/298392.html 这样的地址, 当你打开这个页面之后, 你会发现只有一张图片 , 并且下面有个分页.
点击分页之后, 就知道规律了
- https://www.27270.com/ent/meinvtupian/2018/298392.html
- https://www.27270.com/ent/meinvtupian/2018/298392_2.html
- https://www.27270.com/ent/meinvtupian/2018/298392_3.html
- https://www.27270.com/ent/meinvtupian/2018/298392_4.html
- ....
当你进行多次尝试之后, 你会发现, 后面的链接完全可以靠拼接完成, 如果没有这个页面, 那么他会显示?
好了, 如果你进行了上面的操作, 你应该知道接下来怎么实现啦!
我把所有的代码, 都直接贴在下面, 还是用注释的方式给大家把最重要的地方标注出来
- class Consumer(threading.Thread):
- # 初始化
- def __init__(self):
- threading.Thread.__init__(self)
- self.__headers = {"Referer": "https://www.27270.com/ent/meinvtupian/",
- "Host": "www.27270.com"}
- self.__res = hh.R(headers=self.__headers)
- # 图片下载方法
- def download_img(self,filder,img_down_url,filename):
- file_path = "./downs/{}".format(filder)
- # 判断目录是否存在, 存在创建
- if not os.path.exists(file_path):
- os.mkdir(file_path) # 创建目录
- if os.path.exists("./downs/{}/{}".format(filder,filename)):
- return
- else:
- try:
- # 这个地方 host 设置是个坑, 因为图片为了防止盗链, 存放在另一个服务器上面
- img = requests.get(img_down_url,headers={"Host":"t2.hddhhn.com"},timeout=3)
- except Exception as e:
- print(e)
- print("{} 写入图片".format(img_down_url))
- try:
- # 图片写入不在赘述
- with open("./downs/{}/{}".format(filder,filename),"wb+") as f:
- f.write(img.content)
- except Exception as e:
- print(e)
- return
- def run(self):
- while True:
- global imgs_start_urls,imgs_lock
- if len(imgs_start_urls)>0:
- if imgs_lock.acquire(): # 锁定
- img_url = imgs_start_urls[0] #获取到链接之后
- del imgs_start_urls[0] # 删掉第 0 项
- imgs_lock.release() # 解锁
- else:
- continue
- # https://www.27270.com/ent/meinvtupian/2018/295631_1.html
- #print("图片开始下载")
- img_url = img_url[0]
- start_index = 1
- base_url = img_url[0:img_url.rindex(".")] # 字符串可以当成列表进行切片操作
- while True:
- img_url ="{}_{}.html".format(base_url,start_index) # url 拼接
- content = self.__res.get_content(img_url,charset="gbk") # 这个地方获取内容, 采用了 gbk 编码
- if content is not None:
- pattern = re.compile('[\s\S.]*?img alt="(.*?)".*? src="(.*?)"/>')
- # 匹配图片, 匹配不到就代表本次操作已经完毕
- img_down_url = pattern.search(content) # 获取到了图片地址
- if img_down_url is not None:
- filder = img_down_url.group(1)
- img_down_url = img_down_url.group(2)
- filename = img_down_url[img_down_url.rindex("/")+1:]
- self.download_img(filder,img_down_url,filename) #下载图片
- else:
- print("-"*100)
- print(content)
- break # 终止循环体
- else:
- print("{} 链接加载失败".format(img_url))
- if imgs_lock.acquire(): # 锁定
- imgs_start_urls.append(img_url)
- imgs_lock.release() # 解锁
- start_index+=1 # 上文描述中, 这个地方需要不断进行 + 1 操作
所有的代码都在上面了, 关键的地方我尽量加上了标注, 你可以细细的看一下, 实在看不明白, 就多敲几遍, 因为没有特别复杂的地方, 好多都是逻辑.
最后附上 main 部分的代码, 让我们的代码跑起来
- if __name__ == '__main__':
- img = ImageList()
- urls = img.run()
- for i in range(1,2):
- p = Product(urls)
- p.start()
- for i in range(1,2):
- c = Consumer()
- c.start()
一会过后, 就慢慢收图吧
来源: https://www.2cto.com/kf/201905/806728.html