这篇文章不谈江流所专研的营销与运营,而聊一聊技能学习之路,聊一聊Python这门最简单的编程语言该如何学习,我完成的第一个Python项目,将任意公众号的所有历史文章导出成PDF电子书。
或许我这个Python初学者的学习路径能给你带来启发,或许你产生了要学一门新技术的冲动。
文末附送了学习资源通道。
学习python两度放弃最终有所成
我想要学python五年了,三度捧起python入门书决定开启学习,到第三次才算上道了。
第一次死于安装软件环境,安装碰了太多壁,面对各种困难无从解决而搁置。所以传说三分之一python学习者死于安装软件,此言不虚。
第二次死于一直找不到成就感。从学习python到能够正经用python干些有意义的事情,太费时,于是搁置了。这实际上是学习方法不对,花费太多时间学习知识点,而不是尽早通过解决实际问题学习。
第三次,经过一个星期的死磕,终于完成了爬取任意公众号历史文章并生成PDF文集这件事。获得了成就感,也在死磕的过程中总算入门了python。
Python很火,谁都可以学
想要学一项技能与学成一项技能之间往往有些差距,我们往往想得多,而坚持的少。所以坚持了一件事情,就值得写文章纪念一下,并给他人以启迪。
python火了几年了,有诗为证,“人生苦短,快学python”。
python几乎是各种编程语言里最简单易学的语言,python的应用范围很广,爬虫,数据分析,网站开发,自动化办公,机器学习,云计算,python有大量的库,意味着我们用python编程可以充分利用前人的劳动成果,对程序进行改编,或者运用前人编好的功能模块,进行重新组合,来创造达你的新程序。结果是你用C 可能要写1000行代码,用Python写50行代码就干了同样的事情。
传说有些娱乐圈明星在学python,虽然我目前也没有看到她们的学习成果,可能有的放弃了,可能有的已经在闷声发大财。
知名商人潘石屹,卖了自己的地产,也专心学python去了,他已经坚持微博打卡学python半年多了,每条微博都会提及辅导自己Python的培训机构。我们也不知道他是单纯的行为艺术癖好呢,还是就是想体会中学生刷题的乐趣呢,还是是因为改行做了某培训机构的幕后股东为其站台。
我们学python可没刷半年题的耐心,追求的就是能快速丰富自己的能力,创造更多可能性。
学习Python的最短准备路径
学习Python第一步:装合适的软件环境,买一本合适的入门书
我最开始,看到各种讨论安装哪个python版本,安装什么样的python环境,怎么安装需要用到的各种包,观点不一,而且容易安装失败。这个过程让很多人绝望。
趟过各种坑后,推荐初学者无脑按以下步骤来:
下载安装python3.7版本。其他python3的版本应该也可以。
下载pycharm(感谢北京的张祥帮忙下载安装),我的是JetBrains PyCharm 2018.3.2版本。
搜索下pycharm激活码(激活码经常失效,后台可告诉你怎么找激活码,我试了挺多激活码都无效的)
买本《Python编程快速上手:让繁琐工作自动化(第2版)》,这本书豆瓣评分9.0分,其实作者在他的个人网站发布了该书1.0版的英文版,书中有网站链接,用chrome浏览器的自带翻译也能流畅阅读。但我总觉得学习一项技能要买本书创造点仪式感。
有很多人推荐廖雪峰的Python入门教程,网上可以搜到网站,但我看了下目录,总觉得学习过程过于漫长,离上手项目太遥远,很容易促使新手放弃。
而《Python编程快速上手》这本书,只需要看基础部分那110页,然后看自动化任务部分的两个章节,比如对我要做的爬虫项目有用的是读写文件这个章节和从web抓取信息这个章节。其他内容则是在项目实践过程中,遇到了问题,再回头查找书中的相关内容补充。
这是我目前认为的自学Python的最短路径。
安装各种第三方包
Python的妙处是有大量的第三方开发的包,用于各种功能,你在某个具体项目中,可能会用到其中的几个包,这些包就节省了你大量的工作量。
进入PyCharm,进入File-Settings-Project-Project Interpreter,点击右侧的“ ”号,就可以搜索你需要的包,点击Install安装了。
有时会安装失败,但成功率高于其他方式。如果安装失败,你可以考虑1、等网络状况好了再试试;2、打开VPN再下载;3、在Pycharm界面底部选择Terminal,然后输入pip install 要安装的包。
上GITHUB找相关项目代码
我们初学者,最好的方式是找前人的项目代码研究。通过改编借鉴别人的代码或者思路,来实现自己的目的。
我遵循行业规矩,上GITHUB网站(https://github.com/)搜索相关项目,GITHUB的用法还需要学习下,B站上有一些视频教程。我遵循规矩在GITHUB注册了账号,然后安装了GITHUB Desktop(就是桌面客户端)。
这样,我就可以
1、登陆GITHUB网站搜索相关项目。
2、点击搜索结果查看可能相关的项目。
3、点击绿色按钮Clone or Download,再点击Open in Desktop,将项目保存到本地。
4、在本地保存项目的文件夹里,打开py后缀的文件,查看和修改项目代码。
这里GITHUB Desktop的作用似乎可有可无,但是当我们跨过了初学者的阶段,才会发现它的作用,作用是方便保管项目文件,并显示项目更新,并同步到云上,以便其他人能够同步改进。
GITHUB的项目下载速度很慢,可以试试打开VPN加速。有人推荐国内版的GITHUB“码云”,说下载速度更快,说能把GITHUB上的代码同步到码云然后下载,我注册了,但还没正式用。
梳理项目逻辑
《Python编程快速上手》这本书的一个妙处在于,教给了我们简洁的工作方法。指导我们在开始项目前先要梳理这个程序需要依次实现的动作。
以我完成的爬取公众号历史文章这个项目为例。
第一步:我给自己树立了一个任务,就是要把某一个微信公众号的历史文章都整理到一个pdf文档里。方便我存档信息。
第二步:分解步骤,我认为要完成我这个任务,我需要做三件事。
1、找到获得公众号历史文章url的方式。
2、抓去到这个公众号所有历史文章的url,并保存到一个文件中。
3、读取每个文章的url链接里的文章内容,生成pdf文件。
通过看书,我已经知道了第三步用pdfkit这个包就可以完成,将url等数据保存到csv文件里也可以完成。所以任务变成了,如何抓取到一个公众号所有历史文章的url链接了。
第三步:去GITHUB里找相关项目。
由于微信的挺强的反爬虫能力,很多相关项目都已经不能顺利运行,我们需要寻找相似的项目,分析其逻辑是否可行。
我找到一个项目,里面讨论了获取url的几种方法。根据爬虫所见即所得的原则,你在前端能看到的东西,就一定能用爬虫爬到。
这里就要插入一个知识点了。在chrome浏览器某个页面,按F12键,可以打开浏览器的开发者模式,可以查看这个网页的源代码。只要你在某个页面的源代码中可以找到url,这个 url就可以被爬到。
有一种方法是微信PC端查看公众号的历史文集时可以看到url,但以前我看一个爬虫教程教这种方法时,在手机与电脑连接同一个wifi的过程中,遇到了难处,可能是手机的问题,所以这次我就没敢尝试这种方法。
有一种方法是通过个人微信号登陆后台,搜索任意公众号,可以看到这个公众号发的文章,可以查到文章的永久链接。于是我决定使用这种方法。
网上看到很多文章的方法是通过搜狗搜索引擎搜索公众号文章,进行筛选,然而问题是搜狗只显示100页搜索结果,并且不一定能够做到搜索精确,所以几乎不可能做到下载一个 公众号的所有历史文章,故排除这一思路。
在GITHUB上下载了运用个人微信公众号这个方法的python代码,run了一下失败了。然后就开始读代码,想弄明白这个代码的逻辑是什么样子的,在哪些问题上需要修改。
发现问题,解决问题
有了现成的代码后,我的任务进一步细化。在现有代码的基础上,梳理逻辑,寻找出错的地方,寻找已经过时的地方。我只需要解决现有代码中出现的bug,一个个解决掉,就应该能够爬到url。
我发现这个程序用到的是selenium包,于是从头去了解了selenium这个包的特点,工作原理,入门书里的相关章节。
selenium是一种Python可以用的爬虫工具,特点是模拟人操作电脑完成各种动作,比如自动打开某些页面,进行翻页,点击,动作等,比如解读某个页面的源代码,提取出url等内容。优点是你只要人工能完成的动作就能用selenium完成,缺点是速度慢,容易被反爬虫检测到。
经过阅读代码,我发现这个程序的逻辑是这样的:
1、通过import工具引入需要用到的包。
2、通过def语句,定义一些需要用到的动作。
3、按顺序排列这个程序依次需要执行的动作。
4、通过for循环实现依次完成抓取url的重复性动作。
5、将抓取到的url保存到一个数组,再保存到一个数据库中。
由于我还没有学习python和数据库是如何配合的,我想将数据保存到csv文件中,应该也可以被调用,并且csv文件我可以直接打开查看效果。
于是我将步骤5的代码从保存到数据库改为保存到csv文件。为了怕我反悔,我在代码前添加#号让某些代码不被执行。
以下是原代码中保存到数据库的代码,被我弃用。
with open('data2.pickle', 'wb') as f:
pickle.dump(data, f)
# 读取
with open('data.pickle', 'rb') as f:
b = pickle.load(f)
我改为保存到csv文件的代码。
outputFile = open('outputurl.csv','w',newline='')
outputWriter = csv.writer(outputFile)
outputWriter.writerow(url_title_lst)
outputFile.close()
这段代码确实实现了将数据保存到csv文件,但是格式很有问题,之后放我后来发现问题并更改的代码。
解决bug1:
原代码作者有提醒,为了使用selenium,需要使用与chrome浏览器相对应的chrome driver,为了满足这个要求,也花了我不少功夫。最后百度搜索了一番,终于找到了适配的chrome driver,成功的标志是运行程序时,chrome浏览器被自动启动了。搜索关键词“selenium chrome driver”
看到chrome浏览器自动启动的时候,内心开心,因为这个程序我已经跑通了第一步。
解决bug2:
chrome浏览器自动启动,并打开了微信公众平台的登陆页,然而程序停了。
于是我看代码寻找原因。
def login(username, password):
#打开微信公众号登录页面
driver.get('https://mp.weixin.qq.com/')
driver.maximize_window()
time.sleep(3)
# 自动填充帐号密码
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/form/div[1]/div[1]/div/span/input").clear()
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/form/div[1]/div[1]/div/span/input").send_keys(username)
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/form/div[1]/div[2]/div/span/input").clear()
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/form/div[1]/div[2]/div/span/input").send_keys(password)
time.sleep(1)
#自动点击登录按钮进行登录
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/form/div[4]/a").click()
# 拿手机扫二维码!
time.sleep(15)
猜想是在自动填充的过程中出bug了 。driver.find_element_by_xpath这个句子我似乎见过,翻入门书发现,是selenium里的语句,意思是通过xpath这种方式来寻找元素。
后面的后缀很容易猜,clear()意思是清空内容,sen_keys()意思是填入内容,click()意思是点击,time.sleep(1)意思是程序休息1秒钟。
于是我猜想可能是程序里面定位的位置错了吧。于是百度搜索selenium的find_element_by语句的用法讲解,并搜索xpath相关的内容,xpath后面是什么意思。搜索关键词“selenium find_element_by”
按F12打开开发者工具,点击左侧的箭头按钮,然后光标移到页面中你要定位的的区域,在下方就可以看到当前区域的代码块。此时点击右键,下方代码就定位到这个代码块,在这个代码块再点击右键,copy-xpath,我粘贴到其他地方,发现复制的代码和原代码里xpath()的内容很相似。我猜想,只需要替换xpath()里的代码就可以正确定位了。
替换了xpath里的内容后,再运行程序,自动填充账号密码的动作果真完成了。
注意,原代码是进入微信公众平台登陆页面后就直接填写账号密码了,而我登陆微信公众平台时发现优先项是先扫二维码,于是模仿原代码用driver.find_element_by_xpath语句加了个点击输入账号密码的动作。
def login(username, password):
#打开微信公众号登录页面
driver.get('https://mp.weixin.qq.com/')
#driver.get('https://mp.weixin.qq.com/cgi-bin/loginpage?t=wxm2-login&lang=zh_CN')
driver.maximize_window()
time.sleep(3)
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/div[2]/a").click()
# 自动填充帐号密码
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/div[1]/form/div[1]/div[1]/div/span/input").clear()
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/div[1]/form/div[1]/div[1]/div/span/input").send_keys(username)
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/div[1]/form/div[1]/div[2]/div/span/input").clear()
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/div[1]/form/div[1]/div[2]/div/span/input").send_keys(password)
time.sleep(1)
#自动点击登录按钮进行登录
driver.find_element_by_xpath("//*[@id=\"header\"]/div[2]/div/div/div[1]/form/div[4]/a").click()
# 拿手机扫二维码!
time.sleep(15)
后来我想,既然之后还要扫二维码,其实这段代码可以简化,不用输入账号密码,直接最开始就扫二维码就行了吧。
解决bug3
扫码登陆后,要点击新建图文素材打开一个新页面,每次打开新页面后程序就停止了,chrome浏览器地址栏提醒已拦截不被信任的flash,我猜想是浏览器的问题,于是去浏览器里设置信任,反复设置之后依然出现拦截,猜想这就是传说中的反爬虫机制生效了吧。
于是搜索selenium相关的反爬虫策略如何解决。找了好久解决方案,都没有合适的。后来顿悟,既然扫二维码那一步可以手动完成,那点击链接跳到新页面的这一个动作也手动完成是不是就能跳过反爬虫机制呢。经实验,果然跳过了反爬虫机制。这是个本办法,因为尝试其他办法可能会增加不少工作量,对于初学而言,可以搁置在以后再尝试。
解决bug4
在爬取url时,本应该不断翻页,爬取所有url的,结果程序没有实现翻页。为了解决这个问题,我加了个print数组的动作,以便发现爬取url的动作是否实现,爬取的数据是什么样子。
以及分析翻页这个动作,原代码是怎么实现的。
page_num = int(driver.find_elements_by_class_name('weui-desktop-pagination__num__wrp')[-1].text.split('/')[-1])
# 点击下一页
url_title_lst = get_url_title(driver.page_source)
#print(driver.page_source)
print(url_title_lst)
#for _ in range(1, page_num):
for _ in range(1, page_num):
# i =int(driver.find_elements_by_class_name('weui-desktop-pagination__num__wrp')[-1].text.split('/')[0])
try:
#pagination = driver.find_elements_by_class_name('pagination')[1]
pagination = driver.find_elements_by_class_name('weui-desktop-pagination__nav')[-1]
pagination.find_elements_by_tag_name('a')[-1].click()
time.sleep(5)
url_title_lst = get_url_title(driver.page_source)
于是去查找几个代码的含义,来解读整句代码的意思。了解了原理是提取页码的文本,当当前页码小于总页码数时,就执行翻页动作,当当前页码数等于总页码数时,就停止执行翻页动作。于是按照这个动作逻辑,来查找现有代码的错误。尝试了很多种语法可能,最终找到了跑通这段程序的方式,获得了完整的url。
解决bug5
已经获得了数组,然后要把数据保存到csv文件,发现保存后的格式有问题。由于是数组里包含大量字典数据,保存到csv里的形式是每个单元格保存一个字典,横排保存。
而我们希望呈现的数据是每条数据呈现在一行单元格,竖向排列。
于是百度搜索“python 字典保存到csv文件”
最终完美解决问题。附解决问题后的代码:
下面的url_title_1st是上面生成的url数组里包含字典的数据。
url_list = url_title_lst
with open('outputurl.csv','w',encoding="utf-8",newline='') as f:
writer = csv.DictWriter(f, fieldnames=['date','url', 'title'])
writer.writeheader()
writer.writerows(url_list)
解决bug6
现在任务只剩下通过url地址生成pdf文档了,看起来一步之遥。然而……
我看知乎上或者百度搜索的使用pdfkit生成pdf的代码,要么是只将一个网页转换成pdf文件,要么是在代码中列举多个url。可是我有数百个url,写进代码多麻烦啊。能否直接用python传入csv文件里的所有url,生成pdf呢。
我阅读pdfkit包的说明文档,观察pdfkit的传入url的方法;猜想,只要我传入的是url数组,那应该就可以达到目的。
pdfkit.from_url(['google.com', 'yandex.ru', 'engadget.com'], 'out.pdf')
pdfkit.from_file(['file1.html', 'file2.html'], 'out.pdf')
于是我的任务现在转化为,将之前生成的数组包含字典的数据格式,变为只包含url的数组格式。或者将本来包含多列数据的csv文件,提取其中url那一列生成只有url的数组。
于是百度搜索,关键词“python 数组含字典”,搜索了一阵子,终于看到我想要的解决方案了。
I have a list of dictionaries, and I need to get a list of the values from a given key from the dictionary (all the dictionaries have those same key).
For example, I have:
l = [ { "key": 1, "Val1": 'val1 from element 1', "Val2": 'val2 from element 1' },
{ "key": 2, "Val1": 'val1 from element 2', "Val2": 'val2 from element 2' },
{ "key": 3, "Val1": 'val1 from element 3', "Val2": 'val2 from element 3' } ]
I need to get 1, 2, 3.
Of course, I can get it with:
v=[]
for i in l:
v.append(i['key'])
But I would like to get a nicer way to do so.
解决方案
Using a simple list comprehension (if you're sure every dictionary has the key):
In [10]: [d['key'] for d in l]
Out[10]: [1, 2, 3]
Otherwise you'll need to check for existence first:
In [11]: [d['key'] for d in l if 'key' in d]
Out[11]: [1, 2, 3]
我于是模仿着写了以下代码,果然打印出了url数组:
url_title=[]
for i in url_title_lst:
url_title.append(i['url'])
print(url_title)
如果是从csv文件中提取url数组,又该怎么做呢?
百度搜索“python csv文件中读取数组”,试了网上的一些方式,都bug了,最后找到一种可行方式。
csvrows =[]
csvFileobj = open('out.csvpip','r',encoding='UTF-8')
readerobj = csv.reader(csvFileobj)
for row in readerobj:
if readerobj.line_num == 1:
continue
csvrows.append(row)
csvFileobj.close()
print(csvrows)
这个代码的思路是,由于第一行是表头,跳过不读。一行行的读,每读一行,就把读的这一行加到之前的数组里,这样整个文件读完了,数组也就产生了。
应该还有其他可行方法,就留待以后开展别的项目时再学。
解决bug7
现在有了一个包含多列数据的数组。如何只提取url那一列生成新的数组呢。
再次百度搜索“python 读取数组中某一列”,找到了方法:
>>> a=[[1,2,3],[4,5,6]]
>>> a[0] #取一行
[1, 2, 3]
>>> a[:,0] #尝试用数组的方法读取一列失败
TypeError: list indices must be integers or slices, not tuple
我们需要用列表解析的方法读取一列:
>>> b=[x[0] for x in a]
>>> print(b)
[1, 4]
所以我用以下代码提取数组中的某一列:
url = [x[1] for x in csvrows]
print(url)
打印成功了。
代步车造出来了!
经过一系列的解决bug后,代码终于成功生成了pdf文档。虽然仍然存在一些待解决的问题,但是我心中一种成就感油然而生,总算入门了,总算迈过了容易放弃的痛苦期了。这就是自学python死磕一周的成果。
仍然有一些待解决的问题,比如:
1、同一ip短时间频繁访问一个公众号的历史文章,有时服务器会拒绝访问。
2、没有生成目录。看了pdfkit,似乎需要先有个目录文件才能生成,如何生成这个目录文件呢。
3、没有导出图片。网上看到一些人提出了同样的问题。
4、我导出不到200篇文章的pdf时,可行,但是导出一个有600多篇文章的pdf时失败了,猜想可能是url数组太长了,需要寻找解决方案。
学习一门技术的心得
学习一项技能,都有一条前期平缓后期陡峭的学习曲线,学习一项技能的初期,投入的精力成本虽然较高,但可见的进步却微小,让人缺乏成就感,看不到希望,而一旦跨越了某个转折点,花费同样的精力,获得的进步却变得非常明显。只要能够跨过这一拐点,学习这项技能的成功率就会大幅提升,并且很容易走向精通。
推动一个人行动起来的策略,大体有两个方向:1、给他足够强的驱动力,2、让事情变得更容易办到。
我吸取了前两次学习失败的教训,做了一些改进策略。
1、让学习的启动更加简单。
从安装各种软件,学习复杂的安装步骤。到选择pycharm简化安装步骤。
2、细分目标,将整个项目分解成多个阶段目标,从而降低难度。
将一个项目任务,分解为三个分阶段目标,其中两个阶段我有信心完成,又将剩下的目标分解为需要不断克服的各种bug,每克服一个bug我都能感受到项目离成功更近了一步。
3、简化一些暂时不大需要的学习任务,紧抓核心任务。
我在这个项目中放弃了补习数据库的知识,放弃了补习fidler抓包,以及如何提取json文件中信息的知识,放弃了代理ip反爬虫的知识,放弃了寻找全自动爬虫的解决方法而选择暂时用半自动爬虫。
4、寻找让python学习变得更有价值得学习目标。
我最开始从数据分析的角度学习python,然而少量数据分析可以用excel等工具迅速完成,大数据的分析暂时没有施展机会。后来从爬虫的角度入手python,爬虫是每一个个体都可以学习并应用的领域,起码可以帮助自己备份一些信息资料,进一步地可以通过网上爬到的数据进行数据分析,做一些数据分析作品出来,再进一步地,可以用来运作商业项目。
只要方法对了,你也可以一周入门python,或短时间学会某种技术。
分享转发这篇文章,为你的学习立下一个Flag吧!
在公众号【江流】后台回复关键词“python”可以获得文中提到的自学python用到的资源链接,如何找到可用的pycharm激活码,并限量帮50个读者导出pdf版公众号历史文集。
之后,我可能还会继续在公众号【江流】更新我学习python的进展报告。也希望与更多学习者交流。欢迎后台留言。
,