scrapy 爬取今日头条图片保存至本地
之前用 requests 爬取过今日头条街拍的图片,当时只是爬取每篇文章的缩略图,今天尝试用 scrapy 来大规模爬取街拍详细图片。
分析页面
今日头条的内容是以 Ajax 加载而成的,我们爬取需要的是的 json 数据而非 html。
如上图所示,我们对爬取的 json 数据进行解析,即可得到文章标题,文章详细地址。
def parse(self, response):
text = response.text
json_res = json.loads(text)
if json_res.get('data'):
for item in json_res.get('data'):
# print(item)
if item.get('cell_type') is not None:
continue
title = item.get('title')
if "toutiao" in item.get('article_url'):
it = ToutiaoJiapaiItem()
it['page_url'] = response.url
it['article_url'] = item.get('article_url') # 详细网址
it['title'] = title # 标题
yield Request(url=it['article_url'], headers=self.headers, callback=self.parse_page,
meta={'item': it})
由于 json 数据中的图片列表,内容不全且都为缩略图,因此我们继续向下爬取。
分析详情页
在爬取过程中,发现详细页面大概有三种不同的页面展示。
**1. **
这种文章较为普遍,从上往下欣赏图片。当对该类型的页面进行爬取内容时,虽然返回的是 html 内容,但是排版完全乱了。
利用正则匹配或者BeautifulSoup提取都比较麻烦,在这里,我采用的是对字符串进行截取处理,提取出有效内容。
text = response.text
str_list = text.split('pgc-img')
if str_list != None:
for i in range(1, len(str_list)):
if ""http:" in str_list[i]:
pic_url = str_list[i].split('"')[2]
pic_list.append(pic_url)
2.
这种是点击图片滑动欣赏图集,该页面返回的内容与上一种也不相同,因此需要另一种提取方式。
我们可以获取 JSON.parse 后的内容,将字符串转换为 json 进行提取。
# 利用正则提取图片地址
pattern = re.compile('.*?gallery: JSON.parse\("(.*?)\"\)', re.S)
result = re.search(pattern, text)
if result:
data = json.loads(result.group(1).replace('\\', ''))
if data and 'sub_images' in data.keys():
sub_images = data.get('sub_images')
pic_list = [item.get('url') for item in sub_images]
3.
最后一种是街拍小视频,在这里并没有对视频进行处理。
最后,我们在 pipelines 文件里对获取到的 url 进行下载。
class ToutiaoJiepaiPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['photo_urls']:
yield Request(image_url)
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok,x in results if ok]
if not image_paths:
raise DropItem('图片未下载好')
return item
效果如下。
详细代码如下:
import scrapy
import json
from scrapy_phantomjs.items import ToutiaoJiapaiItem
from urllib.parse import urlencode
from scrapy import Request
import re
class ToutiaoJiepaiSpider(scrapy.Spider):
name = 'toutiao_jiepai'
headers = {
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36'
}
def start_requests(self):
# yield Request(url='https://www.toutiao.com/a6673629078630695427/', headers=self.headers,
# callback=self.parse_page)
for i in range(0, 2):
offset = i * 20
params = {
'aid': '24',
'app_name': 'web_search',
'offset': str(offset),
'format': 'json',
'keyword': '街拍',
'autoload': 'true',
'count': '20',
'en_qc': '1',
'cur_tab': '1',
'from': 'search_tab',
'pd': 'synthesis'
}
url = 'https://www.toutiao.com/api/search/content/?' + urlencode(params)
yield Request(url=url, headers=self.headers, callback=self.parse)
def parse(self, response):
text = response.text
json_res = json.loads(text)
if json_res.get('data'):
for item in json_res.get('data'):
if item.get('cell_type') is not None:
continue
title = item.get('title')
if "toutiao" in item.get('article_url'):
it = ToutiaoJiapaiItem()
it['page_url'] = response.url
it['article_url'] = item.get('article_url') # 详细网址
it['title'] = title # 标题
yield Request(url=it['article_url'], headers=self.headers, callback=self.parse_page,
meta={'item': it})
def parse_page(self, response):
item = response.meta['item']
text = response.text
pic_list = []
str_list = text.split('pgc-img')
if str_list != None:
for i in range(1, len(str_list)):
if ""http:" in str_list[i]:
pic_url = str_list[i].split('"')[2]
pic_list.append(pic_url)
# 利用正则提取图片地址
pattern = re.compile('.*?gallery: JSON.parse\("(.*?)\"\)', re.S)
result = re.search(pattern, text)
if result:
data = json.loads(result.group(1).replace('\\', ''))
if data and 'sub_images' in data.keys():
sub_images = data.get('sub_images')
pic_list = [item.get('url') for item in sub_images]
item['photo_urls'] = pic_list
yield item