The start nodes will be the ones with an in-degree of 0. The end nodes will have an out-degree of 0.
Here's an example digraph:
Note that nodes A, B, and C are start nodes, numbered nodes have edges pointing both in and out, and nodes X, Y, and Z are end nodes. Here's the code to generate that graph:
import matplotlib.pyplot as plt
import networkx as nx
D = nx.DiGraph()
edges = [('A', '1'), ('B', '2'), ('C', '3'), ('1', '2'),
('2', '3'), ('3', '4'), ('3', '5'), ('5', '6'),
('4', '5'), ('5', '4'), ('5', '2'), ('2', '6'),
('6', '4'), ('4', 'X'), ('5', 'Y'), ('6', 'Z')]
D.add_edges_from(edges)
pos = nx.spring_layout(D)
fig, ax = plt.subplots(figsize=(10, 5))
nx.draw_networkx_nodes(D, pos, ax=ax, node_size=500, node_color="#acddc5", edgecolors='g')
nx.draw_networkx_labels(D, pos, ax=ax, font_weight='bold', font_size=12)
nx.draw_networkx_edges(D, pos, ax=ax, edgelist=edges, edge_color="g")
plt.show()
Now I can iterate over all of the nodes looking for the ones with in-degree of 0 and out-degree of 0 using the in_degree and out_degree functions. (Both functions return an iterator of tuples that contain (node, degree) of each node in the graph.)
start_nodes = [n for n, d in D.in_degree() if d == 0]
end_nodes = [n for n, d in D.out_degree() if d == 0]
print("Start nodes:", start_nodes)
print("End nodes:", end_nodes)
Output:
Start nodes: ['A', 'B', 'C']
End nodes: ['X', 'Y', 'Z']