8Y_jtXR>4wRM%>4{c+5dCbb7y
zR%R=Yj{Ddj?+9VEn_5I3{J`ZR-dYCFJYnjIX=6)%VDFny7Ur<%ZF76twtRjL7fRYU
zSEolAdy}e_C5R0R5f9d}c0AfZ1tPJ{omkJB%t>dqtYZs)IG#29mK_LlZGA=nAv3>1
zku-u{c9o(z?KF3~gQ33deC;Y2^ijzyM?eNb$Mu11lJY<)!}P&{^;0mvkB|R=ocnQo
z^A0j4GRu-Eosx|6y~?A_tftjE5J(&}e3(>f$@CqSvnKpUs*GX`w~_Pj`gULeAmj%T
z*kd`4Or)#n{v7}DQ!JVdIB{R(C8gS#^g!H=GmUY=_e{Ur_mHa&_WW^-L$`L*jWZIG
zI2M5|8k;Omqai&QE9bCB9lFtsuZOE&=(#fcC5P3g_}xkQ5p+Paeyb(UA?&HQBJb=L
z*!`aEaLeO*-~>kF3}t4mHcGc$p55kVR$N;0H9!w@Y7Uew6(3rRLJs=(Cu
zXT46wzxy|BCIuHbmaeq4L*s-N>%n}Zku-P0#m33@3zERtIxVc@VO8DdsIGLCjOxiU
zjIBXNDE)p)k#6@ND=5Vzfw
z{nh(P0mnFWw6Rd*=R~JbCq;s|Du@@o;P16cIuEM61`BMNqpPVDRVea3yBAwA#!*=T&oSGEio<>ga(Wh60{QN)ZxX9s
z8l8=pI+X%eEm;b>ap^K9%T4PuW?pF+uYYA{RDO6qkI-LH{-k{JY26pZ$)Jk!7T|v_
z_LSZVwS1d+F}wG;4Z@&NVs(*Zhp}l`q=~w>onj9`S)46|`tndj`w~BX0F_N_P=tV-<)Le!ljIt9B=^brEC%=BR
z`Qf)X4pC0}lj3e0v;w=PR+fI>+{{Cy?%hT0nO%%C|2
zvJh^@DyL`y0%xU|JiFGg6a7x~7+)j{@cA3Zm{Mdv3xzoX`7K
zLSgZfCZRo?`u5IFSJaU530>65^`0=kfm8*Ld@lx``Bv!m`{`!h-^@^;vQ|X0Slyde
z(!RkN7Yyk_8McmyUFoGlwIJcdNh=ekFE&US!%g(t{Q;?vqqdOhFT;S_!`}RXkfM71
zUiIpb$HeY8yh06;#yNs}Fn@8U1NdJx{mt2^_J)kXbkH>lyE_(SKrAKpdlfM
?{P&>QMjn=STx-?b&>4dWt=Bf0JdEBRSrpdykPw)W~U(g
zqBbW^zE|`G-CAcBsnhlx3;rcZLB;2EOpa-P2p-`Xmey3|3W<{8C5u3Js_7Yf@SFB8
zb3O5dy67z(UPBCzu9=gX&B6!`>1*Qj@5EBjv&+~Ndss$+}M2l;N
z1PZb)X#^oPbQ-2fNbgu=t7<)W)byF|({DDZD}8p>mHnc|z9WxRdVApTUN4w?p=y=(
zBOO6a$qSCT#x`H2Mrr+cMHf_ds9vC@sB2WPUdKHi3-4?4f0$41dE_&VMa`=q@
z;r()@qlYZwU9DfrY?+9dNRd6(*a!rx?bxY?F93j`*qKF7wcF;wC+CiqHgi|MafPldIdgB)u{3$0rqkM2cuapm@DOV)MS-4
ztwrkmS2c3v#uN*{|
zr6fnv<7gt5lB(bP)fE0J<;?-nK$Pm32V41J8l+sxYkT9Hq=f|De`@%y$MKzZ$)UHUw!|eK+i|dT=L~@JmJXq5mDzmFOJYdY>D6&e&5aV
zyn3E#8h^AqFo;CrG_o|{b+KQEcFPN07-x~c5Ms6kFUiJ^=$)-M!{AD=&at-l?9RW~
zg$gFas+8>^*S&0bZ~rtw^Kj#8CF}^kIb6HbLIXnT{+5j=bWjELH~J>2Ke4B@!R)v-eNF9PsQFbG;9NMAK%;~qxTr!j;
zg@S7`_(4+SB+Qi^TsnjBtLc4N#Ru_4%>guH(O=?STC&hPFyh`aK9iwb-Smu{%Vq1_
z%R_HD6shtIh`7k7q*Ek6IoV6PsH9GbPI>-j&vBk}uez)CT2M>TZR?vIhJFPLvafp?
zZFCuFuKRlRdyeA({#Y`VTdg}AF(|wr!%8m?K0(gI!|(X&Og^3CkLiwzl
z4s)I9AIPKJRjDMgJpN2-Q|!m^qDuv9Bkkq;rYG)yw2oemY*&QmI(Mtx(ce=h91O99
zPDwXtQ`B-(3a!f;U=;1clxp0%NmcCCQVqzWxxhk4Jz||Np$SQa#!}9Okl6wc0i>K)pLkbrsLQVsPsJyVNB4dWh)($e8?Hb
zNrFoTRVGakNn<0weG
z-zYFzvRQa3`x6vhO}%@WpJIp
zCBdr{eTCZWP-h~=55n_#2tKeVfaY#Q5{vu?0K_LrovzBihC6>9$XRgTo8As7-pS6f
z+Cd3Cw7w66_=ySG>N{5Bqp;z6v^usYf8ST8
z1b%6g#!sH1dd>-iZs~%GNpV$Q?P^F_cnsS2%Jj7k3@#cvBlCSm#N^}Ajc-h9jnZH`
z`;9cCWC0{N>EDBT1Qc8N`u64+7KG+()948bcD%cv0P0VHh}~gzC}un_KAV)?K&XLS
zJnHKO=%x)m+1YI|P#bY|*1j*iaQB_Vo|uF!f4SPy*RFdluI~2?H5$2Ec}6Zq$s%k*
zbL974>;71nTI56f9%c8&gX~TJ^RDboYUKgn@b%W7g@uZ%YW6X5ruy~8*mf@_+&Wy#
z{c|ZJ5;22fkjxSW0%{39G{KEbHZ}*ZGPREBw5;m^-
z-1l>uu^EkMRyD+c%s23NKE>$Zd{g7?TB9+&o5}WsH0Di34_M^pJFS7kHX-kwS(J9%J?TAEVv!#-=Au12VQS6<&+5z$0+R%D;zI_9C4-HrOqJQ15Pcq@?%z3
zH|YKd7npT7*in=xBqp=oDMru!ReJ+dxynCLI4vb!K|P2XD`h`&@e1rEer(JCt6zvt
zdvzu-MCWd3EK`jUxIb%W@tfJe{mt7(PI-KINDci?vsI_eT3vX%ybUa8kqiumQ!~N
zMc{!qTCYYXwIj&EQ;db-6R4ya!@1rfPY8#zjHE#fHH^|Gs;vvv|IAK<=kf>g}Ayha}iIl{wb%;
z50CNq@Q^SbrCQ@>ES_)HBTi)So9B$y8(*$Vr676E#pT3GLlrBG1O%i2OUP|-71~^3
zGW&6Z)p2(GU^VtIzJ_=D?8-ze4J7PypIca@HE+#*{64W{u^x?35-&*13x^(-Z}y1o
z%*!Kd@nY@Mw=8s?=D{Ajwus~(V>I7RsG`gVoo*eqxRz(>k|2A1@WSI=aSF$axreUMG+%DGzV!Lwto<1@fdj4Kei
zI&jGa#ZBC|$#z9H5P%gb#G!8a($WBFqt_l_me!@A8kL0SyUsSJi1RQI)Z_S2ahIXQ
z;87X6*74f7JKzJK=e|bhp+yOi*(=d#*6HaZ_HzVr*SzYxtOC`I{Q!(a?DWQ+viHyw
zv1ut#O))+}={}B-FrTmZ{`@!MDNveG5Udx@$<%cu=ir{jRr@6H)*EO}-EEr(ge}~d
z+7Df*=GqZ{q0UZ%efxsC1@K?ST|zRfH3U{dZ$tF9a$LJ0+53nmEB%lumPi1tPcXtI
z22ZH>JnGe}7g%UG9;}_*s?BXzW-u#V#;jaU*lE@J5xSMjI_|{cDSkT{_kX+o`c#$N
z%Ly$~Li-}d_md}t2{FRv5~rAG&e~>+BV<}F~_mIZ?0h+bvLgVIfC
z_f^W#wXUu2(9{g@(M&?fWv)MrExXhP2F;UYRjiOkq3JR?M3#qZhF=^x0o-S~79V^8
zAai9O>vvgbCPI%I67y-%x}0oMTrxXTN%sp$C8zGsl~V!Drhrg0c$oOsAoaGo1MJ3e
z4Zkx_u-UQOZ1a>ezzo6i1~VeLye+j9j4zOM<;@|HCrJc_u|PPJWUmU-J^D4rzXnjJ
zWlm&~U|1WpG?*7QP_z~jgKM+I(G$(N_Hf!n#5vy!tqhXUJhs?%V8r-=q3eD%x0}O=
z>%Qr#g5vG+4vS!jeJw4)6JFRpT7yJJ}@
z<%CIDAK9>5pmCfI@YL*q`eDI^p8`ikm>Z3pr@e{ej}(7sy<%W2G)ru*cV_n7n>fiT)iNe9s8ujJ={+Fvnt7bDp*oDg{nR;h
zO7O6~2fbvkns@xcKDC=+C#DGT=JDo9eC^i56akqB7clGOyn1kYHs!SUB>-h4ZG#8V
zLgsi*%&LGUl@;k&IJAl7j+6o*6XA9O&72x%K#K)?wpyL8n`HOUzU#U+KMDD8Qdu=H
zV(0?iN;YehZl4A5b6mf7`}&YKojR+pUR0uxyDcbTG+!!$+fMuYHy+%Y#Fk}N&;9<5
zd)Bh>K7Mfeg7KgtbNvyJds3r>dEmfi8a{+ujnJ)@Iq0y5r1WLWNA}xE1=2!?ye)8<
zPHH(9y)CKj>8&-4L1esf6Vjj!eTl!xJ-u0(^528DtlG=&O&g3mwWMJI^Y8fy|Sw+KY?uYQT$Y4IZ5Fw0s;b0jHQVW
zsQI*NZjXz;Cl$+x`c{Jzc&g_|p%lDEF
z-t?-L5}r0mRS0N}M6K87sEwie!c7D&nXNzzZ(f0`+nyO(AcVNg1B{1@FI$
zkBPBm)9n@E40<$Cltkt;Sn?VSV{M?4`{kD3eapTS{N5N^#X7Od=#n&Ou7#1)2Epx+
z)(CZrlRH{(V+!g+-s?nlkQ*&s&dDV9k%p89(@gu~TEcZ5>tFP3rFsHU3+JQxNzh1H
z4jcjt>_3w`J{|dNXkZ^A|6KpY@%aB0QGbg5#r60@6YjpX8+v%FF*z&v;Y7A
literal 0
HcmV?d00001
diff --git a/q2_1.py .py b/q2_1.py .py
new file mode 100644
index 0000000..8f37e33
--- /dev/null
+++ b/q2_1.py .py
@@ -0,0 +1,74 @@
+import requests
+import json
+from bs4 import BeautifulSoup
+
+# 题目强制要求请求头
+url = "https://exam.detr.top/exam-b/movies"
+headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
+}
+
+# 仅一次请求,符合题目要求
+resp = requests.get(url, headers=headers)
+resp.encoding = "utf-8"
+html_text = resp.text
+
+# 保存原始网页 movies.html
+with open("movies.html", "w", encoding="utf-8") as f:
+ f.write(html_text)
+print("✅ 已保存网页源码 movies.html")
+
+# 优先尝试直接解析接口JSON(接口真实返回格式)
+movie_list = []
+data_id = None
+try:
+ api_data = json.loads(html_text)
+ data_id = api_data.get("data_id")
+ movie_list = api_data.get("movies", [])
+ print("✅ 识别为JSON接口,直接读取数据")
+except json.JSONDecodeError:
+ # 若为HTML表格页面,执行原bs4解析逻辑
+ print("识别为HTML表格页面,使用BeautifulSoup解析")
+ soup = BeautifulSoup(html_text, "html.parser")
+ # 提取页面data_id
+ if soup.body and "data-id" in soup.body.attrs:
+ data_id = soup.body["data-id"]
+ # 提取表格行
+ all_tr = soup.find_all("tr")
+ for tr in all_tr[1:]:
+ td_list = tr.find_all("td")
+ if len(td_list) >= 8:
+ # 增加类型转换容错
+ def safe_int(txt):
+ try:
+ return int(txt.strip())
+ except:
+ return 0
+ def safe_float(txt):
+ try:
+ return float(txt.strip())
+ except:
+ return 0.0
+ movie = {
+ "id": safe_int(td_list[0].get_text()),
+ "title": td_list[1].get_text(strip=True),
+ "director": td_list[2].get_text(strip=True),
+ "year": safe_int(td_list[3].get_text()),
+ "rating": safe_float(td_list[4].get_text()),
+ "duration": safe_int(td_list[5].get_text()),
+ "genre": td_list[6].get_text(strip=True),
+ "actors_count": safe_int(td_list[7].get_text())
+ }
+ movie_list.append(movie)
+
+print(f"页面data_id: {data_id}")
+print(f"一共抓取到 {len(movie_list)} 部电影")
+
+# 组装并保存 movies.json
+save_data = {
+ "data_id": data_id,
+ "movies": movie_list
+}
+with open("movies.json", "w", encoding="utf-8") as f:
+ json.dump(save_data, f, ensure_ascii=False, indent=2)
+print("✅ movies.json 写入完成")
\ No newline at end of file
diff --git a/q2_2.py .py b/q2_2.py .py
new file mode 100644
index 0000000..436ed44
--- /dev/null
+++ b/q2_2.py .py
@@ -0,0 +1,38 @@
+import json
+
+# 读取生成好的电影文件
+with open("movies.json", "r", encoding="utf-8") as f:
+ data = json.load(f)
+movie_list = data["movies"]
+
+if len(movie_list) == 0:
+ print("❌ 未抓取到任何电影数据,请先运行pachong.py!")
+else:
+ # ① 找出评分最高、最低电影
+ sorted_movies = sorted(movie_list, key=lambda x: x["rating"])
+ lowest_movie = sorted_movies[0]
+ highest_movie = sorted_movies[-1]
+ print("\n① 评分最高&最低电影:")
+ print(f"评分最低:{lowest_movie['title']} {lowest_movie['rating']}")
+ print(f"评分最高:{highest_movie['title']} {highest_movie['rating']}")
+
+ # ② 统计各类型电影数量(字典输出)
+ genre_count = {}
+ for m in movie_list:
+ g = m["genre"]
+ genre_count[g] = genre_count.get(g, 0) + 1
+ print("\n② 各类型电影数量:", genre_count)
+
+ # ③ 统计各导演电影数量(字典输出)
+ director_count = {}
+ for m in movie_list:
+ d = m["director"]
+ director_count[d] = director_count.get(d, 0) + 1
+ print("\n③ 各导演电影数量:", director_count)
+
+ # ④ 统计2020年(含)以后上映电影
+ count_2020 = 0
+ for m in movie_list:
+ if m["year"] >= 2020:
+ count_2020 += 1
+ print(f"\n④ 2020年(含)后上映电影总数:{count_2020}")
\ No newline at end of file