记一次学习爬虫的过程(上)

 记录一次学习爬虫的过程,从0到爬取今日头条,微信公众号。文章将详细的记录遇到的写爬虫过程中遇到的所有问题,以及尝试的各种解决问题的方案。

开发需求

某日收到运营同学提出的开发需求邮件,大概内容就是,获取部分友商网站的,今日头条头条号的,微信公众号的文章(统称第三方文章),包括文章标题,描述,文章连接,作者,发布时间。第三方内容更新时,需要及时获取。

   这是让我写爬虫呀,爬虫是个好东西,但是我不会呀!学呗!

基础知识准备

 在公司一直用python开发,那爬虫也用python写呗,python写爬虫,早有耳闻requests和BeautifulSoup包的大名,数据库就用mysql。这样获取数据,解析数据,存储数据的都有了。
 那么
 先看一波requests, BeautifulSoup的文档,-.-!!!
requests 文档
BeautifulSoup 文档

送分题

  发现友商的网站都是这样古老的风格,据说都是某框架一键搭建的,基本没有反爬措施。这不就是传说中的送分题,开撸!


image.png

先在chrome中观察一下页面,很容易发现,页面都是这个get请求返回的,并且数据的结构很简单,整体是一个table,每一行是tbody id 为normalthread_*


image.png

image.png

那开始撸代码喽

html = requests.get(url).text
# 最好先判断一下请求返回值是否为200
soup = BeautifulSoup(html, 'html.parser', from_encoding='utf-8')
contents = soup.find_all('tbody', attrs={"id": re.compile(r"normalthread_*")})
for content in contents:
    try:
        title_link = content.find('a', class_='xst')
        title = title_link.string
        post_url = title_link.get('href')

        author = content.find('td', class_='by').find('a').string
        post_time = content.find('td', class_='by').find('em').find('span').string

        ......

    except Exception as e:
        logger.info(e)

其他友商的网站也都是类似的简单结构,就不一一赘述了,打完收工!

今日头条头条号

失败的尝试

用爬取友商网站的方式爬取今日头条,结果很僵硬,服务器只会返回{"message": "error", "has_more": false, "data": []},显然,今日头条是有反爬的,这可难倒了我这个新手,抓包分析今日头条的请求。

image.png

发现我们要的数据都是在这个https://www.toutiao.com/c/user/article/?page_type=1&user_id=6609164111&max_behot_time=0&count=20&as=A1158AB24157B44&cp=5A21379BC444AE1&_signature=WJGs4hASArGHJ2lKzejNLViRrP的请求中以json格式返回,这个请求的前面的参数都能明白是什么意思,后面三个 as,cp,_signature 参数估计就是一种校验。多请求几次不难发现这三个参数每次都不一样,显然和时间有关。
思路:这三个参数肯定是某段js代码生成,我们只需要找到这段代码,翻译成python,每次请求带上这三个参数,就可以获得我们想要的数据的json格式,在解析json就简单了。撸
发现页面中引入了这个js文件,找到这个代码的文件格式化。搜索_signature关键字
image.png

image.png

幸运,我们要的三个参数都在这里,as,cp是ascp.getHoney()方法的返回值。_signature,是TAC.sign(userInfo.id + "" + c.params.max_behot_time).
userInfo.id ,c.params.max_behot_time 大概都是我们url中的参数。
继续查找getHoney方法,发现还是在个文件中有这个方法

i.getHoney = function() {
        var t = Math.floor((new Date).getTime() / 1e3)
          , i = t.toString(16).toUpperCase()
          , e = md5(t).toString().toUpperCase();
        if (8 != i.length)
            return {
                as: "479BB4B7254C150",
                cp: "7E0AC8874BB0985"
            };
        for (var s = e.slice(0, 5), o = e.slice(-5), n = "", a = 0; 5 > a; a++)
            n += s[a] + i[a];
        for (var l = "", r = 0; 5 > r; r++)
            l += i[r + 3] + o[r];
        return {
            as: "A1" + n + i.slice(-3),
            cp: i.slice(0, 3) + l + "E1"
        }
    }

翻译成python 代码

def getASCP(time=None):
   time = int(math.floor(time.time()))

   e = hex(time).upper()[2:]

   md5 = hashlib.md5()
   md5.update(str(time).encode(encoding='utf-8'))
   i = md5.hexdigest().upper()

   if len(e) != 8:
       AS = '479BB4B7254C150'
       CP = '7E0AC8874BB0985'
       return AS, CP
   n = i[0:5]
   a = i[-5:]
   s = ''
   r = ''
   for o in range(5):
       s += n[o] + e[o]
       r += e[o + 3] + a[o]

   AS = 'A1' + s + e[-3:]
   CP = e[0:3] + r + 'E1'
   return AS, CP

那就还差_signature参数,也就是要找到TAC.sign方法。继续查找,依旧在这个文件中发现一段代码

Function(function(t) {
    return '�e(e,a,r){�(b[e]||(b[e]=t("x,y","�x "+e+" y"�)(r,a)}�a(e,a,r){�(k[r]||(k[r]=t("x,y","�new x[y]("+Array(r+1).join(",x[�y]")�(1)+")"�)(e,a)}�r(e,a,r){�n,t,s={},b=s.d=r?r.d+1:0;for(s["$"+b]=s,t=0;t<b;t�)s[n="$"+t]=r[n];for(t=0,b=s�=a�;t<b;t�)s[t]=a[t];�c(e,0,s)}�c(t,b,k){�u(e){v[x�]=e}�f�{�g=�,t�ing(b�g)}�l�{try{y=c(t,b,k)}catch(e){h=e,y=l}}for(�h,y,d,g,v=[],x=0;;)switch(g=�){case 1:u(!�)�4:�f��5:u(�(e){�a=0,r=e�;���{�c=a<r;�c&&u(e[a�]),c}}(���6:y=�,u(�(y��8:if(g=�,l��g,g=�,y===c)b+=g;else if(y!==l)�y�9:�c�10:u(s(���11:y=�,u(�+y)�12:for(y=f�,d=[],g=0;g<y�;g�)d[g]=y.charCodeAt(g)^g+y�;u(String.fromCharCode.apply(null,d��13:y=�,h=delete �[y]�14:���59:u((g=�)?(y=x,v.slice(x-=g,y�:[])�61:u(�[�])�62:g=�,k[0]=65599*k[0]+k[1].charCodeAt(g)>>>0�65:h=�,y=�,�[y]=h�66:u(e(t[b�],�,���67:y=�,d=�,u((g=�).x===c?r(g.y,y,k):g.apply(d,y��68:u(e((g=t[b�])<"<"?(b--,f�):g+g,�,���70:u(!1)�71:�n�72:�+f��73:u(parseInt(f�,36��75:if(�){b��case 74:g=�<<16>>16�g�76:u(k[�])�77:y=�,u(�[y])�78:g=�,u(a(v,x-=g+1,g��79:g=�,u(k["$"+g])�81:h=�,�[f�]=h�82:u(�[f�])�83:h=�,k[�]=h�84:�!0�85:�void 0�86:u(v[x-1])�88:h=�,y=�,�h,�y�89:u(��{�e�{�r(e.y,arguments,k)}�e.y=f�,e.x=c,e}�)�90:�null�91:�h�93:h=��0:��;default:u((g<<16>>16)-16)}}�n=this,t=n.Function,s=Object.keys||�(e){�a={},r=0;for(�c in e)a[r�]=c;�a�=r,a},b={},k={};�r'.replace(/[�-�]/g, function(i) {
        return t[15 & i.charCodeAt(0)]
    })
}("v[x++]=�v[--x]�t.charCodeAt(b++)-32�function �return �))�++�.substr�var �.length�()�,b+=�;break;case �;break}".split("�")))()('gr$Daten Иb/s!l y͒yĹg,(lfi~ah`{mv,-n|jqewVxp{rvmmx,&eff�kx[!cs"l".Pq%widthl"@q&heightl"vr*getContextx$"2d[!cs#l#,*;?|u.|uc{uq$fontl#vr(fillTextx$$龘ฑภ경2<[#c}l#2q*shadowBlurl#1q-shadowOffsetXl#$$limeq+shadowColorl#vr#arcx88802[%c}l#vr&strokex[ c}l"v,)}eOmyoZB]mx[ cs!0s$l$Pb<k7l l!r&lengthb%^l$1+s$j�l  s#i$1ek1s$gr#tack4)zgr#tac$! +0o![#cj?o ]!l$b%s"o ]!l"l$b*b^0d#>>>s!0s%yA0s"l"l!r&lengthb<k+l"^l"1+s"j�l  s&l&z0l!$ +["cs\'(0l#i\'1ps9wxb&s() &{s)/s(gr&Stringr,fromCharCodes)0s*yWl ._b&s o!])l l Jb<k$.aj;l .Tb<k$.gj/l .^b<k&i"-4j!�+& s+yPo!]+s!l!l Hd>&l!l Bd>&+l!l <d>&+l!l 6d>&+l!l &+ s,y=o!o!]/q"13o!l q"10o!],l 2d>& s.{s-yMo!o!]0q"13o!]*Ld<l 4d#>>>b|s!o!l q"10o!],l!& s/yIo!o!].q"13o!],o!]*Jd<l 6d#>>>b|&o!]+l &+ s0l-l!&l-l!i\'1z141z4b/@d<l"b|&+l-l(l!b^&+l-l&zl\'g,)gk}ejo{�cm,)|yn~Lij~em["cl$b%@d<l&zl\'l $ +["cl$b%b|&+l-l%8d<@b|l!b^&+ q$sign ', [TAC = {}]);

脸一黑,这是什么鬼,混淆过?尚且不说看不懂,就算看懂了翻译成python也有难度,经过几次挣扎,彷徨,做了个艰难的决定,放弃这个方案,毕竟世上无难事,只要肯放弃。

另辟蹊径

之前的想法是行不通了,只有另辟蹊径呗,继续查找一下解决方案。遇见
selenium

( 那天下午阳光很好,你穿了件连衣裙 ^.^ )

了解selenium 文档(非官方)
selenium能获得ajax后的html文件,这样我们的问题就解决了,不用管访问目标网站时到底执行了,只需要把最后生成目标网页抓出来。解析即可。
开始撸代码

    driver = webdriver.Chrome()
    # driver = webdriver.PhantomJS(executable_path="/Users/wxf/Downloads/phantomjs-2.1.1-macosx/bin/phantomjs")

    for url in urls:
        logger.info("start jrtt url {}".format(url))
        # 使用浏览器请求页面
        driver.get(url)

        # 等待ajax加载,这里一定要等下,刚开始就没加载出来,还误以为方案不可行
        driver.refresh()  # 刷新一下,会打开浏览器,浏览器有一个被测试工具控制的提示,堵塞页面加载。
        WebDriverWait(driver, timeout=10).until(
            lambda x: x.find_element_by_class_name('left'))

        #selenium也能解析html标签,前面已经学习了BeautifulSoup,这里就还是用BeautifulSoup
        soup = BeautifulSoup(driver.page_source, 'html.parser', from_encoding='utf-8')
        contents = soup.find('div', class_='left').find_all('div', class_='rbox-inner')
        for content in contents:
            try:
                # print(content)
                title_link = content.find('a', class_='link title')
                title = title_link.string
                post_url = JRTT_URL + title_link.get('href')

                ......
            except Exception as e:
                logger.info(e)

这样头条号内容的抓取都不在话下了。

微信公众号文章

打开运营同学给的微信链接,发现已过期,微信的链接有时效性,再让给个新的,打开,发现提示只能在微信客户端打开。问,你们平时怎么在pc端浏览器里打开微信公众号的链接,得到结论
http://weixin.sogou.com/

image.png

思路,请求http://weixin.sogou.com/
我们想要爬取的公众号,然后模拟浏览器点击事件。就能获得这个公众号最近10篇文章。

    driver = webdriver.Chrome()
    # 使用浏览器请求页面
    for wx_gzh_name in wx_gzh_names:
        url = "http://weixin.sogou.com/weixin?type=1&s_from=input&query={}&ie=utf8&_sug_=n&_sug_type_=".format(
            wx_gzh_name)
        driver.get(url)

        driver.find_element_by_class_name('img-box').click()
        all_handle = driver.window_handles
       #点击公众号列表某行,会新开一个公众号详情tab页,我们切换到新开的tab页
        driver.switch_to.window(all_handle[1])

        try:
            WebDriverWait(driver, timeout=10).until(
                lambda x: x.find_element_by_class_name('weui_media_title'))

            soup = BeautifulSoup(driver.page_source, 'html.parser', from_encoding='utf-8')
            wx_profile_nickname = soup.find('strong', class_='profile_nickname').text
            contents = soup.find_all('div', class_='weui_media_bd')
            for content in contents:
                title_link = content.find('h4', class_='weui_media_title')
                title = title_link.text
        except Exception as e:
            print(e)
        finally:
            driver.close()
            time.sleep(random.randint(5, 10))  # 防止太请求太频繁

        driver.switch_to.window(all_handle[0])

微信公众号的文章就到手了

剩下的问题:IP被禁,部署

正在沾沾自喜时,发现微信公众号爬取不了了,让其他同事访问搜狗微信搜索,发现每次搜索都要填一个简单的验证码。公司公用同一个公网ip,那说明我们的ip被封了呗。
解决方案思路:
1.识别验证码
2.ip池
程序写完了,一直在本地跑,是不行的,怎么部署到服务器,代码执行依赖浏览器,服务器上是没有浏览器的。

IP被禁,部署还有其他问题将在下篇在记载!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,846评论 25 707
  • 爬虫文章 in 简书程序员专题: like:128 - Python 爬取落网音乐 like:127 - 【图文详...
    treelake阅读 29,536评论 33 638
  • 真不能相信2016年已经过去了,再填写什么表格啊,信息啊都变成2017开头了。 我怎么如此的没有感觉2016就过去...
    luckyacotor2阅读 146评论 0 0
  • 投射:1投射過一個最舒適悠閒的假期! 2投射愈來愈多客戶支持我的業績! 3投射財富不斷湧入我的戶口! 感賞:1感賞...
    謝奕鋒阅读 118评论 0 0
  • 我再一次见到了她,她没有说话,只是远远的望了一眼,那表情充满了哀怨,还有一丝丝鬼魅。我有些百思不得其解,她难道是在...
    Ronjay杰阅读 233评论 0 0