Python实战: 将博客自动上传到各大中文博客平台

jekyll博客一键发布

Posted by zhida on May 22, 2017

背景介绍

为了更好的掌握Python这门语言,自己开了一个项目专门用来实战,主要的功能是:遍历 jekyll文件夹的博客,对文件进行简单的处理,然后程序自动登录各大博客平台,发布文章。

项目地址:jekyll-blog-auto-upload , 目前只完成了 segmentfaul 的站点,后续会增加CSDN、云栖等其他博客平台的支持。

主要流程

列出主要的几个流程,完整代码可以去项目地址拉取

文件处理

遍历_posts文件夹,查找符合 yyyy-mm-dd-XXX.md 的文件,保存到字典中,key值是文件路径,value值是文件的上层文件夹名称(因为我的博客是以文件夹做分类的),后续用来做标签处理

def dirCb(self, dirname):
	for line in os.listdir(dirname):
		filename = os.path.abspath(os.path.join(dirname, line))
		if (os.path.isdir(filename)):
			self.dirCb(filename)
		else:
			pattern = re.compile(r"(\d+)-(\d+)-(\d+)-(.{0,}.md)")
			result = pattern.findall(filename)
			if (len(result) != 0):
				tags = filename.split('_posts')[1]
				# print tags
				tagname = ''
				for tag in tags.split(os.sep):
					if (tag != '' and len(pattern.findall(tag)) == 0):
						tagname = tagname + '|' + tag
				tagname = tagname[1:]
				self.filenameList[filename] = tagname

登录

项目的根路径有一个 config.json文件,配置了各大站点的元数据,包括账号密码,提交的URL地址等,使用 requests库对请求进行登录提交。

def login(self):
	res = ''
	while (res == ''):
		try:
			data = self._prepare_login_form_data()
			res = self._session.post(self.loginUrl, data=data, timeout=10)
			print res
			if (res.status_code == 200):
				self.saveHeader()
				print 'login succ'
				return 0
			else:
				print 'login fail'
		
		except ValueError, e:
			print e
			print 'use cached login is succ'
			return 'succ'
		except requests.exceptions.ConnectionError:
			print 'requests.exceptions.ConnectionError  try again'
			time.sleep(2)
			print 'sleep over'
			continue

def _prepare_login_form_data(self):
	
	# 封装返回数据
	form = {
		'username': str(self.username),
		'password': str(self.password),
		'remember': "1"
	}
	print form
	return form

获取标签信息

对前面获取的标签信息,进行提交获取到相应的tagId,当标签过长或者找不到符合网站条件的标签时,进行健壮性处理

	def getTags(self, tagname):
		## 标签处理
		self._session.headers['Referer'] = 'https://segmentfault.com/write?freshman=1'
		if (self._session.headers.has_key('Origin')):
			del self._session.headers['Origin']
			del self._session.headers['Content-Length']
			del self._session.headers['Content-Type']
		
		res = ''
		while res == '':
			try:
				# print 'getTags:'
				# print self.tagUrl + urllib.quote_plus(tagname)
				res = self._session.get(self.tagUrl + urllib.quote_plus(tagname), timeout=5)
			except:
				time.sleep(2)
				print 'ag'
				continue
		
		print res.text
		if (len(res.json()['data']) == 0):
			print 'could not found tag,ag'
			## 如果最后没有找到合适的标签 统一置为 后台类型
			if(len(tagname) == 1):
				tagname = '后台 '
			print tagname[0:len(tagname) - 1]
			return self.getTags(tagname[0:len(tagname) - 1])
		else:
			print res.json()['data'][0]['name']
			return res.json()['data'][0]['id']

保存草稿 & 发布文章

segmentfaul对文章提交之前会进行一次保存,保存草稿获取的ID号用于提交文章

def postArticle(self, filename):
	self._session.headers['Referer'] = 'https://segmentfault.com/write?freshman=1'
	formdata = self._prepare_post_form_data(filename)
	if (formdata == None):
		return None
	else:
		print 'post article data:'
		print formdata
	
	res = ''
	while (res==''):
		try:
			res = self._session.post(self.postUrl, data=formdata, timeout=10)
			print res
			print res.text
			if (res.json()['status'] == 0):
				print '文章发布成功:' + formdata['title']
				self.addbloglist(filename)
			elif(res.json()['status'] == 1):
				print '文章发布失败:'
				print res.json()['data']
				shuzu = res.json()['data']
				for sz in shuzu:
					if(str(sz) != 'form'):
						print sz['captcha']
			else:
				print '文章发布失败:' + formdata['title'] + "  -:" + res.json()
		except:
			print '发布异常--'
			time.sleep(3)
			continue
	
	print '-- post end --'

def _prepare_post_form_data(self, filename):
	
	## 获取提交文章需要的信息
	draftData = self.extractFile(filename)
	if (draftData == None):
		return None
	
	print '-- save draft --'
	artId = ''
	res = ''
	while (res == ''):
		try:
			res = self._session.post(self.draftUrl, data=draftData, timeout=10)
			status = res.json()['status']
			if (status == 0):
				artId = res.json()['data']
				print '保存草稿成功'
			else:
				print '保存草稿失败'
				return None
		except:
			print '保存草稿出现异常'
			time.sleep(2)
			continue
	
	del draftData['do']
	del draftData['aticleId']
	draftData['license'] = '1'
	draftData['draftId'] = artId
	draftData['createId'] = ''		
	return draftData

文章清单

对上传过的文章进行保存,防止多次重复提交

def addbloglist(self, filename):
	path = os.path.abspath(os.path.join(sys.path[0], '../bloglist/' + self.bloglist))
	file = open(path, 'a')
	file.write('\r\n'+filename.split('/')[len(filename.split('/'))-1])
	file.close()

遇到的问题

在学习Python语言做这个项目的时候,遇到了很多问题,有些是语言本身不熟悉导致的,有些是不同的博客平台的提交逻辑不同导致,列一些主要的问题。

请求登录post 报404

这种情况一般是没有添加正确的header头导致的,处理方式是按照Chrome的header报文格式,完全复制出来,然后粘贴到代码中,一般都能够通过请求,然后逐步删减看看哪些是服务端必须校验的头部信息。

中文解析异常:SyntaxError: Non-ASCII character ‘\xe6’ but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

没有设置正确的字符格式,在文件的头部添加# -*- coding: utf-8 -*-即可

requests.exceptions.SSLError: HTTPSConnectionPool

这个问题是我的电脑一般都会使用 shadowsocks代理,导致访问 https 的协议的时候报错,关掉就是了

提示:需要验证码登录 、 登录频繁请求
  • 这种情况一般都得把验证码下载到本地,然后手动输入,
  • 或者添加 ocr 自动识图程序,后续会在开源项目中添加
  • 更新你的cookie信息,避免同一个cookie信息总是频繁访问
  • post的 request form信息错误
知乎一直提示验证码会话失效

出现这种情况是因为我把登录的流程分在了两个文件,a.py(验证码获取) 和 b.py(发起登录请求)中,这个时候就会造成登录的请求关于验证码的cookie信息不存在,报错,正确的方式是合并在同一个文件中处理或者更新 session信息

python load open file ValueError: Expecting , delimiter

加载的文件 ,里面内容格式错误

使用保存的Header或者Cookie对请求头进行赋值无效。

对于Header和cookie的赋值 不能直接等于字符串 要用 udpdate 和json.load 格式

requests.session().cookies 和 requests.session().headers.[‘cookie’]格式问题
  • requests.session().cookies.getDict() 是字典数据结构
  • requests.session().headers.[‘cookie’]是=连接的是键值对
UnicodeEncodeError: ‘ascii’ codec can’t encode character u’\xa0’ in position 20: ordinal not in range(128)
String.encode('utf-8')
urllib and “SSL: CERTIFICATE_VERIFY_FAILED” Error
req = urllib2.Request(url, headers={ 'X-Mashape-Key': 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' })
gcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)  # Only for gangstars
info = urllib2.urlopen(req, context=gcontext).read()
Max retries exceeded with url: VerifiedHTTPSConnection object at 0x1028ff9d0>: Failed to establish a new connection: [Errno 60] Operation timed out’

比较经常遇到的一个问题:equests.exceptions.ConnectionError: HTTPSConnectionPool(host=’segmentfault.com’, port=443): Max retries exceeded with url: (Caused by NewConnectionError(‘<urllib3.connection.VerifiedHTTPSConnection object at 0x1028ff9d0>: Failed to establish a new connection: [Errno 60] Operation timed out’,))

  • header信息严格按照浏览器抓取的来 及时是 / 也不能漏掉
  • 进行重试处理
    resp = ''
    ur = 'https://segmentfault.com/'
    while(resp == ''):
      try:
          print  'home stat'
      except :
          print 'home fail'
          time.sleep(5)
          continue